diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index 29f82cafa74b1..6add03050d132 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -192,6 +192,16 @@ async function createSnippetFile(scope: string, defaultPath: URI, quickInputServ '\t// \t],', '\t// \t"description": "Log output to console"', '\t// }', + '\t//', + '\t// You can also restrict snippets to specific files using include/exclude patterns:', + '\t// "Test snippet": {', + '\t// \t"scope": "javascript,typescript",', + '\t// \t"prefix": "test",', + '\t// \t"body": "test(\'$1\', () => {\\n\\t$0\\n});",', + '\t// \t"include": ["**/*.test.ts", "*.spec.ts"],', + '\t// \t"exclude": ["**/temp/*.ts"],', + '\t// \t"description": "Insert test block"', + '\t// }', '}' ].join('\n')); @@ -218,6 +228,15 @@ async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileS '\t// \t],', '\t// \t"description": "Log output to console"', '\t// }', + '\t//', + '\t// You can also restrict snippets to specific files using include/exclude patterns:', + '\t// "Test snippet": {', + '\t// \t"prefix": "test",', + '\t// \t"body": "test(\'$1\', () => {\\n\\t$0\\n});",', + '\t// \t"include": ["**/*.test.ts", "*.spec.ts"],', + '\t// \t"exclude": ["**/temp/*.ts"],', + '\t// \t"description": "Insert test block"', + '\t// }', '}' ].join('\n'); await textFileService.write(pick.filepath, contents); diff --git a/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts index beea5ed367d86..fa5f708780322 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts @@ -39,7 +39,8 @@ export class ApplyFileSnippetAction extends SnippetsAction { return; } - const snippets = await snippetService.getSnippets(undefined, { fileTemplateSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true }); + const resourceUri = editor.getModel().uri; + const snippets = await snippetService.getSnippets(undefined, resourceUri, { fileTemplateSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true }); if (snippets.length === 0) { return; } diff --git a/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts index 5d71cf5e8cf28..338e0ff196a13 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts @@ -127,13 +127,13 @@ export class InsertSnippetAction extends SnippetEditorAction { if (name) { // take selected snippet - snippetService.getSnippets(languageId, { includeNoPrefixSnippets: true }) + snippetService.getSnippets(languageId, undefined, { includeNoPrefixSnippets: true }) .then(snippets => snippets.find(snippet => snippet.name === name)) .then(resolve, reject); } else { // let user pick a snippet - resolve(instaService.invokeFunction(pickSnippet, languageId)); + resolve(instaService.invokeFunction(pickSnippet, languageId, editor.getModel().uri)); } }); diff --git a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts index e9f9a63d98ab0..393e0c2bab1aa 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts @@ -23,7 +23,7 @@ export async function getSurroundableSnippets(snippetsService: ISnippetsService, model.tokenization.tokenizeIfCheap(lineNumber); const languageId = model.getLanguageIdAtPosition(lineNumber, column); - const allSnippets = await snippetsService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets }); + const allSnippets = await snippetsService.getSnippets(languageId, model.uri, { includeNoPrefixSnippets: true, includeDisabledSnippets }); return allSnippets.filter(snippet => snippet.usesSelection); } @@ -54,12 +54,13 @@ export class SurroundWithSnippetEditorAction extends SnippetEditorAction { const snippetsService = accessor.get(ISnippetsService); const clipboardService = accessor.get(IClipboardService); - const snippets = await getSurroundableSnippets(snippetsService, editor.getModel(), editor.getPosition(), true); + const model = editor.getModel(); + const snippets = await getSurroundableSnippets(snippetsService, model, editor.getPosition(), true); if (!snippets.length) { return; } - const snippet = await instaService.invokeFunction(pickSnippet, snippets); + const snippet = await instaService.invokeFunction(pickSnippet, snippets, model.uri); if (!snippet) { return; } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts index 53802851eca5b..ede209f332c29 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCodeActionProvider.ts @@ -88,7 +88,7 @@ class FileTemplateCodeActionProvider implements CodeActionProvider { return undefined; } - const snippets = await this._snippetService.getSnippets(model.getLanguageId(), { fileTemplateSnippets: true, includeNoPrefixSnippets: true }); + const snippets = await this._snippetService.getSnippets(model.getLanguageId(), model.uri, { fileTemplateSnippets: true, includeNoPrefixSnippets: true }); const actions: CodeAction[] = []; for (const snippet of snippets) { if (actions.length >= FileTemplateCodeActionProvider._MAX_CODE_ACTIONS) { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 4bd9ab7344643..abc6ff72545b1 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -105,7 +105,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider { const triggerCharacterLow = context.triggerCharacter?.toLowerCase() ?? ''; const languageId = this._getLanguageIdAtPosition(model, position); const languageConfig = this._languageConfigurationService.getLanguageConfiguration(languageId); - const snippets = new Set(await this._snippets.getSnippets(languageId)); + const snippets = new Set(await this._snippets.getSnippets(languageId, model.uri)); const suggestions: SnippetCompletion[] = []; for (const snippet of snippets) { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts b/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts index 205fd707021d1..99839fac08a1a 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts @@ -12,8 +12,9 @@ import { ThemeIcon } from '../../../../base/common/themables.js'; import { Event } from '../../../../base/common/event.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { URI } from '../../../../base/common/uri.js'; -export async function pickSnippet(accessor: ServicesAccessor, languageIdOrSnippets: string | Snippet[]): Promise { +export async function pickSnippet(accessor: ServicesAccessor, languageIdOrSnippets: string | Snippet[], resourceUri?: URI): Promise { const snippetService = accessor.get(ISnippetsService); const quickInputService = accessor.get(IQuickInputService); @@ -26,7 +27,7 @@ export async function pickSnippet(accessor: ServicesAccessor, languageIdOrSnippe if (Array.isArray(languageIdOrSnippets)) { snippets = languageIdOrSnippets; } else { - snippets = (await snippetService.getSnippets(languageIdOrSnippets, { includeDisabledSnippets: true, includeNoPrefixSnippets: true })); + snippets = (await snippetService.getSnippets(languageIdOrSnippets, resourceUri, { includeDisabledSnippets: true, includeNoPrefixSnippets: true })); } snippets.sort((a, b) => a.snippetSource - b.snippetSource); diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 226fccbff8e0c..46ccc93bb5969 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -75,6 +75,20 @@ const snippetSchemaProperties: IJSONSchemaMap = { description: { description: nls.localize('snippetSchema.json.description', 'The snippet description.'), type: ['string', 'array'] + }, + include: { + markdownDescription: nls.localize('snippetSchema.json.include', 'A list of glob patterns to include the snippet for specific files, e.g. `["**/*.test.ts", "*.spec.ts"]` or `"**/*.spec.ts"`.'), + type: ['string', 'array'], + items: { + type: 'string' + } + }, + exclude: { + markdownDescription: nls.localize('snippetSchema.json.exclude', 'A list of glob patterns to exclude the snippet from specific files, e.g. `["**/*.min.js"]` or `"*.min.js"`.'), + type: ['string', 'array'], + items: { + type: 'string' + } } }; diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.ts b/src/vs/workbench/contrib/snippets/browser/snippets.ts index ae096afb2a6ec..6a7dfdaa5bcc4 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.ts @@ -5,6 +5,7 @@ import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; import { SnippetFile, Snippet } from './snippetsFile.js'; +import { URI } from '../../../../base/common/uri.js'; export const ISnippetsService = createDecorator('snippetService'); @@ -27,7 +28,7 @@ export interface ISnippetsService { updateUsageTimestamp(snippet: Snippet): void; - getSnippets(languageId: string | undefined, opt?: ISnippetGetOptions): Promise; + getSnippets(languageId: string | undefined, resourceUri?: URI, opt?: ISnippetGetOptions): Promise; - getSnippetsSync(languageId: string, opt?: ISnippetGetOptions): Snippet[]; + getSnippetsSync(languageId: string, resourceUri?: URI, opt?: ISnippetGetOptions): Snippet[]; } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index fb981d5bbb75f..7d843f8f3db23 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -16,6 +16,8 @@ import { relativePath } from '../../../../base/common/resources.js'; import { isObject } from '../../../../base/common/types.js'; import { Iterable } from '../../../../base/common/iterator.js'; import { WindowIdleValue, getActiveWindow } from '../../../../base/browser/dom.js'; +import { match as matchGlob } from '../../../../base/common/glob.js'; +import { Schemas } from '../../../../base/common/network.js'; class SnippetBodyInsights { @@ -113,6 +115,8 @@ export class Snippet { readonly source: string, readonly snippetSource: SnippetSource, readonly snippetIdentifier: string, + readonly include?: string[], + readonly exclude?: string[], readonly extensionId?: ExtensionIdentifier, ) { this.prefixLow = prefix.toLowerCase(); @@ -138,6 +142,34 @@ export class Snippet { get usesSelection(): boolean { return this._bodyInsights.value.usesSelectionVariable; } + + isFileIncluded(resourceUri: URI): boolean { + const uriPath = resourceUri.scheme === Schemas.file ? resourceUri.fsPath : resourceUri.path; + const fileName = basename(uriPath); + + const getMatchTarget = (pattern: string): string => { + return pattern.includes('/') ? uriPath : fileName; + }; + + if (this.exclude) { + for (const pattern of this.exclude.filter(Boolean)) { + if (matchGlob(pattern, getMatchTarget(pattern), { ignoreCase: true })) { + return false; + } + } + } + + if (this.include) { + for (const pattern of this.include.filter(Boolean)) { + if (matchGlob(pattern, getMatchTarget(pattern), { ignoreCase: true })) { + return true; + } + } + return false; + } + + return true; + } } @@ -147,6 +179,8 @@ interface JsonSerializedSnippet { scope?: string; prefix: string | string[] | undefined; description: string; + include?: string | string[]; + exclude?: string | string[]; } function isJsonSerializedSnippet(thing: unknown): thing is JsonSerializedSnippet { @@ -192,7 +226,7 @@ export class SnippetFile { } private _filepathSelect(selector: string, bucket: Snippet[]): void { - // for `fooLang.json` files all snippets are accepted + // for `fooLang.json` files apply inclusion/exclusion rules only if (selector + '.json' === basename(this.location.path)) { bucket.push(...this.data); } @@ -286,6 +320,24 @@ export class SnippetFile { scopes = []; } + let include: string[] | undefined; + if (snippet.include) { + if (Array.isArray(snippet.include)) { + include = snippet.include; + } else if (typeof snippet.include === 'string') { + include = [snippet.include]; + } + } + + let exclude: string[] | undefined; + if (snippet.exclude) { + if (Array.isArray(snippet.exclude)) { + exclude = snippet.exclude; + } else if (typeof snippet.exclude === 'string') { + exclude = [snippet.exclude]; + } + } + let source: string; if (this._extension) { // extension snippet -> show the name of the extension @@ -314,6 +366,8 @@ export class SnippetFile { source, this.source, this._extension ? `${relativePath(this._extension.extensionLocation, this.location)}/${name}` : `${basename(this.location.path)}/${name}`, + include, + exclude, this._extension?.identifier, )); } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 187615df1deee..4c681e481e62e 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -265,7 +265,7 @@ export class SnippetsService implements ISnippetsService { return this._files.values(); } - async getSnippets(languageId: string | undefined, opts?: ISnippetGetOptions): Promise { + async getSnippets(languageId: string | undefined, resourceUri?: URI, opts?: ISnippetGetOptions): Promise { await this._joinSnippets(); const result: Snippet[] = []; @@ -289,10 +289,10 @@ export class SnippetsService implements ISnippetsService { } } await Promise.all(promises); - return this._filterAndSortSnippets(result, opts); + return this._filterAndSortSnippets(result, resourceUri, opts); } - getSnippetsSync(languageId: string, opts?: ISnippetGetOptions): Snippet[] { + getSnippetsSync(languageId: string, resourceUri?: URI, opts?: ISnippetGetOptions): Snippet[] { const result: Snippet[] = []; if (this._languageService.isRegisteredLanguageId(languageId)) { for (const file of this._files.values()) { @@ -302,10 +302,10 @@ export class SnippetsService implements ISnippetsService { file.select(languageId, result); } } - return this._filterAndSortSnippets(result, opts); + return this._filterAndSortSnippets(result, resourceUri, opts); } - private _filterAndSortSnippets(snippets: Snippet[], opts?: ISnippetGetOptions): Snippet[] { + private _filterAndSortSnippets(snippets: Snippet[], resourceUri?: URI, opts?: ISnippetGetOptions): Snippet[] { const result: Snippet[] = []; @@ -322,6 +322,10 @@ export class SnippetsService implements ISnippetsService { // isTopLevel requested but mismatching continue; } + if (resourceUri && !snippet.isFileIncluded(resourceUri)) { + // include/exclude settings don't match + continue; + } result.push(snippet); } diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index 43dd710d128e9..1e6db4d54c18f 100644 --- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -93,7 +93,7 @@ export class TabCompletionController implements IEditorContribution { const model = this._editor.getModel(); model.tokenization.tokenizeIfCheap(selection.positionLineNumber); const id = model.getLanguageIdAtPosition(selection.positionLineNumber, selection.positionColumn); - const snippets = this._snippetService.getSnippetsSync(id); + const snippets = this._snippetService.getSnippetsSync(id, model.uri); if (!snippets) { // nothing for this language diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 6dd26435753b5..14afd71b76a94 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -22,14 +22,19 @@ import { CompletionModel } from '../../../../../editor/contrib/suggest/browser/c import { CompletionItem } from '../../../../../editor/contrib/suggest/browser/suggest.js'; import { WordDistance } from '../../../../../editor/contrib/suggest/browser/wordDistance.js'; import { EditorOptions } from '../../../../../editor/common/config/editorOptions.js'; +import { URI } from '../../../../../base/common/uri.js'; class SimpleSnippetService implements ISnippetsService { declare readonly _serviceBrand: undefined; constructor(readonly snippets: Snippet[]) { } - getSnippets() { - return Promise.resolve(this.getSnippetsSync()); + getSnippets(languageId?: string, resourceUri?: URI) { + return Promise.resolve(this.getSnippetsSync(languageId!, resourceUri)); } - getSnippetsSync(): Snippet[] { + getSnippetsSync(languageId?: string, resourceUri?: URI): Snippet[] { + // Filter snippets based on resourceUri if provided + if (resourceUri) { + return this.snippets.filter(snippet => snippet.isFileIncluded(resourceUri)); + } return this.snippets; } getSnippetFiles(): any { @@ -1057,4 +1062,106 @@ suite('SnippetsService', function () { assert.strictEqual(result2.suggestions.length, 1); } }); + + test('getSnippetsSync - include pattern', function () { + snippetService = new SimpleSnippetService([ + new Snippet(false, ['fooLang'], 'TestSnippet', 'test', '', 'snippet', 'test', SnippetSource.User, generateUuid(), ['**/*.test.ts']), + new Snippet(false, ['fooLang'], 'SpecSnippet', 'spec', '', 'snippet', 'test', SnippetSource.User, generateUuid(), ['**/*.spec.ts']), + new Snippet(false, ['fooLang'], 'AllSnippet', 'all', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + ]); + + // Test file should only get TestSnippet and AllSnippet + let snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.test.ts')); + assert.strictEqual(snippets.length, 2); + assert.ok(snippets.some(s => s.name === 'TestSnippet')); + assert.ok(snippets.some(s => s.name === 'AllSnippet')); + + // Spec file should only get SpecSnippet and AllSnippet + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.spec.ts')); + assert.strictEqual(snippets.length, 2); + assert.ok(snippets.some(s => s.name === 'SpecSnippet')); + assert.ok(snippets.some(s => s.name === 'AllSnippet')); + + // Regular file should only get AllSnippet + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.ts')); + assert.strictEqual(snippets.length, 1); + assert.strictEqual(snippets[0].name, 'AllSnippet'); + + // Without URI, all snippets should be returned (backward compatibility) + snippets = snippetService.getSnippetsSync('fooLang'); + assert.strictEqual(snippets.length, 3); + }); + + test('getSnippetsSync - exclude pattern', function () { + snippetService = new SimpleSnippetService([ + new Snippet(false, ['fooLang'], 'ProdSnippet', 'prod', '', 'snippet', 'test', SnippetSource.User, generateUuid(), undefined, ['**/*.min.js', '**/dist/**']), + new Snippet(false, ['fooLang'], 'AllSnippet', 'all', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + ]); + + // Regular .js file should get both snippets + let snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.js')); + assert.strictEqual(snippets.length, 2); + + // Minified file should only get AllSnippet (ProdSnippet is excluded) + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.min.js')); + assert.strictEqual(snippets.length, 1); + assert.strictEqual(snippets[0].name, 'AllSnippet'); + + // File in dist folder should only get AllSnippet + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/dist/bundle.js')); + assert.strictEqual(snippets.length, 1); + assert.strictEqual(snippets[0].name, 'AllSnippet'); + }); + + test('getSnippetsSync - include and exclude patterns together', function () { + snippetService = new SimpleSnippetService([ + new Snippet(false, ['fooLang'], 'TestSnippet', 'test', '', 'snippet', 'test', SnippetSource.User, generateUuid(), ['**/*.test.ts', '**/*.spec.ts'], ['**/*.perf.test.ts']), + ]); + + // Regular test file should get the snippet + let snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.test.ts')); + assert.strictEqual(snippets.length, 1); + + // Spec file should get the snippet + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.spec.ts')); + assert.strictEqual(snippets.length, 1); + + // Performance test file should NOT get the snippet (excluded) + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.perf.test.ts')); + assert.strictEqual(snippets.length, 0); + + // Regular file should NOT get the snippet (not included) + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.ts')); + assert.strictEqual(snippets.length, 0); + }); + + test('getSnippetsSync - filename-only patterns (no path separator)', function () { + // Patterns without '/' should match on filename only (like files.associations) + snippetService = new SimpleSnippetService([ + new Snippet(false, ['fooLang'], 'TestSnippet', 'test', '', 'snippet', 'test', SnippetSource.User, generateUuid(), ['*.test.ts']), + new Snippet(false, ['fooLang'], 'ConfigSnippet', 'config', '', 'snippet', 'test', SnippetSource.User, generateUuid(), ['config.json']), + ]); + + // *.test.ts should match any file ending in .test.ts regardless of path + let snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/src/foo.test.ts')); + assert.strictEqual(snippets.length, 1); + assert.strictEqual(snippets[0].name, 'TestSnippet'); + + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/other/deep/path/bar.test.ts')); + assert.strictEqual(snippets.length, 1); + assert.strictEqual(snippets[0].name, 'TestSnippet'); + + // config.json should match filename exactly + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/config.json')); + assert.strictEqual(snippets.length, 1); + assert.strictEqual(snippets[0].name, 'ConfigSnippet'); + + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/deep/nested/path/config.json')); + assert.strictEqual(snippets.length, 1); + assert.strictEqual(snippets[0].name, 'ConfigSnippet'); + + // myconfig.json should NOT match config.json pattern + snippets = snippetService.getSnippetsSync('fooLang', URI.file('/project/myconfig.json')); + assert.strictEqual(snippets.length, 0); + }); });