Change sanitized characters to support Windows paths (#1529)

Fixed #1526
This commit is contained in:
Riccardo
2025-10-03 13:21:08 +02:00
committed by GitHub
parent 40740db416
commit 54a6ffdf01
4 changed files with 110 additions and 13 deletions

View File

@@ -1,6 +1,28 @@
import { asAbsolutePaths } from './path'; import { asAbsolutePaths, fromFsPath } from './path';
describe('path utils', () => { describe('path utils', () => {
describe('fromFsPath', () => {
it('should normalize backslashes in relative paths', () => {
const [path] = fromFsPath('areas\\dailies\\2024\\file.md');
expect(path).toBe('areas/dailies/2024/file.md');
});
it('should handle mixed separators in relative paths', () => {
const [path] = fromFsPath('areas/dailies\\2024/file.md');
expect(path).toBe('areas/dailies/2024/file.md');
});
it('should preserve forward slashes in relative paths', () => {
const [path] = fromFsPath('areas/dailies/2024/file.md');
expect(path).toBe('areas/dailies/2024/file.md');
});
it('should normalize backslashes in Windows absolute paths', () => {
const [path] = fromFsPath('C:\\workspace\\file.md');
expect(path).toBe('/C:/workspace/file.md');
});
});
describe('asAbsolutePaths', () => { describe('asAbsolutePaths', () => {
it('returns the path if already absolute', () => { it('returns the path if already absolute', () => {
const paths = asAbsolutePaths('/path/to/test', [ const paths = asAbsolutePaths('/path/to/test', [

View File

@@ -16,13 +16,16 @@ export function fromFsPath(path: string): [string, string] {
let authority: string; let authority: string;
if (isUNCShare(path)) { if (isUNCShare(path)) {
[path, authority] = parseUNCShare(path); [path, authority] = parseUNCShare(path);
path = path.replace(/\\/g, '/');
} else if (hasDrive(path)) { } else if (hasDrive(path)) {
path = '/' + path[0].toUpperCase() + path.substr(1).replace(/\\/g, '/'); path = '/' + path[0].toUpperCase() + path.substr(1);
} else if (path[0] === '/' && hasDrive(path, 1)) { } else if (path[0] === '/' && hasDrive(path, 1)) {
// POSIX representation of a Windows path: just normalize drive letter case // POSIX representation of a Windows path: just normalize drive letter case
path = '/' + path[1].toUpperCase() + path.substr(2); path = '/' + path[1].toUpperCase() + path.substr(2);
} }
// Always normalize backslashes to forward slashes (filesystem → POSIX)
path = path.replace(/\\/g, '/');
return [path, authority]; return [path, authority];
} }

View File

@@ -493,17 +493,17 @@ foam_template:
'foam-vscode.create-note' 'foam-vscode.create-note'
); );
// Title with many invalid characters (excluding / which is preserved for directories): \#%&{}<>?*$!'":@+`|= // Title with many invalid characters
const resolver = new Resolver(new Map(), new Date()); const resolver = new Resolver(new Map(), new Date());
resolver.define('FOAM_TITLE', 'Test\\#%&{}<>?*$!\'"Title:@+`|='); resolver.define('FOAM_TITLE', 'Test#%&{}<>?*$!\'"Title@+`|=');
const result = await engine.processTemplate(trigger, template, resolver); const result = await engine.processTemplate(trigger, template, resolver);
// All invalid characters should become dashes: Test + 14 invalid chars + Title + : + @+`|= (6 more total) // All invalid characters should become dashes
expect(result.filepath.path).toBe('Test--------------Title------.md'); expect(result.filepath.path).toBe('Test-------------Title-----.md');
// Content should remain unchanged // Content should remain unchanged
expect(result.content).toContain('# Test\\#%&{}<>?*$!\'"Title:@+`|='); expect(result.content).toContain('# Test#%&{}<>?*$!\'"Title@+`|=');
}); });
it('should not affect FOAM_TITLE when not used in filepath', async () => { it('should not affect FOAM_TITLE when not used in filepath', async () => {
@@ -569,7 +569,7 @@ Date and title combination.`,
const result = await engine.processTemplate(trigger, template, resolver); const result = await engine.processTemplate(trigger, template, resolver);
// Entire resolved filepath should be sanitized // Entire resolved filepath should be sanitized
expect(result.filepath.path).toBe('2024/03/Note-With-Invalid-Chars.md'); expect(result.filepath.path).toBe('2024/03/Note:With-Invalid-Chars.md');
// Content should use original FOAM_TITLE // Content should use original FOAM_TITLE
expect(result.content).toContain('# Note:With|Invalid*Chars'); expect(result.content).toContain('# Note:With|Invalid*Chars');
@@ -601,5 +601,78 @@ foam_template:
expect(result.filepath.path).toBe('notes/ValidTitle123.md'); expect(result.filepath.path).toBe('notes/ValidTitle123.md');
expect(result.content).toContain('# ValidTitle123'); expect(result.content).toContain('# ValidTitle123');
}); });
it('should preserve backslashes as directory separators (Windows-style paths)', async () => {
const { engine } = await setupFoamEngine();
// Simulate a resolved filepath with Windows-style backslash separators
const template: Template = {
type: 'markdown',
content: `# MyNote`,
metadata: new Map([
['filepath', 'areas\\dailies\\2024\\MyNote.md'], // Already resolved, has backslashes
]),
};
const trigger = TriggerFactory.createCommandTrigger(
'foam-vscode.create-note'
);
const resolver = new Resolver(new Map(), new Date());
const result = await engine.processTemplate(trigger, template, resolver);
// Backslashes should be normalized to forward slashes
expect(result.filepath.path).toBe('areas/dailies/2024/MyNote.md');
expect(result.content).toContain('# MyNote');
});
it('should normalize mixed forward and backslashes', async () => {
const { engine } = await setupFoamEngine();
// Simulate a resolved filepath with mixed separators
const template: Template = {
type: 'markdown',
content: `# MyNote`,
metadata: new Map([
['filepath', 'areas/dailies\\2024/MyNote.md'], // Mixed separators
]),
};
const trigger = TriggerFactory.createCommandTrigger(
'foam-vscode.create-note'
);
const resolver = new Resolver(new Map(), new Date());
const result = await engine.processTemplate(trigger, template, resolver);
// Both separators should be normalized to forward slashes
expect(result.filepath.path).toBe('areas/dailies/2024/MyNote.md');
expect(result.content).toContain('# MyNote');
});
it('should sanitize invalid characters while normalizing backslash separators', async () => {
const { engine } = await setupFoamEngine();
// Simulate a resolved filepath with backslash separator and invalid chars
const template: Template = {
type: 'markdown',
content: `# Note:With*Invalid`,
metadata: new Map([['filepath', 'areas\\Note:With*Invalid.md']]), // Backslash + invalid chars
};
const trigger = TriggerFactory.createCommandTrigger(
'foam-vscode.create-note'
);
const resolver = new Resolver(new Map(), new Date());
const result = await engine.processTemplate(trigger, template, resolver);
// Backslash normalized to forward slash, invalid chars sanitized
expect(result.filepath.path).toBe('areas/Note:With-Invalid.md');
expect(result.content).toContain('# Note:With*Invalid');
});
}); });
}); });

View File

@@ -14,14 +14,13 @@ import { URI } from '../core/model/uri';
/** /**
* Characters that are invalid in file names * Characters that are invalid in file names
* Based on UNALLOWED_CHARS from variable-resolver.ts but excluding forward slash * Based on UNALLOWED_CHARS from variable-resolver.ts but excluding filepaths
* which is needed for directory separators in filepaths * related chars
*/ */
const FILEPATH_UNALLOWED_CHARS = '\\#%&{}<>?*$!\'":@+`|='; const FILEPATH_UNALLOWED_CHARS = '#%&{}<>?*$!\'"@+`|=';
/** /**
* Sanitizes a filepath by replacing invalid characters with dashes * Sanitizes a filepath by replacing invalid characters with dashes
* Note: Forward slashes (/) are preserved for directory separators
* @param filepath The filepath to sanitize * @param filepath The filepath to sanitize
* @returns The sanitized filepath * @returns The sanitized filepath
*/ */