diff --git a/src/builtin-addons/core/template-completion-provider.ts b/src/builtin-addons/core/template-completion-provider.ts index 9231078a..43110ade 100644 --- a/src/builtin-addons/core/template-completion-provider.ts +++ b/src/builtin-addons/core/template-completion-provider.ts @@ -31,6 +31,7 @@ import { isScopedAngleTagName, isSpecialHelperStringPositionalParam, isFirstParamOfOnModifier, + isAttributeValueConcatMustachePath, } from '../../utils/ast-helpers'; import { listComponents, @@ -576,6 +577,13 @@ export default class TemplateCompletionProvider { const yields = await this.getParentComponentYields(focusPath.parent); completions.push(...yields); + } else if (isAttributeValueConcatMustachePath(focusPath)) { + const ignoredHelpers = ['helper', 'modifier', 'component']; + const items = await this.getMustachePathCandidates(root); + const localCandidates = await this.getLocalPathExpressionCandidates(uri, originalText); + + completions.push(...items.filter((el) => el.detail === 'helper' && !ignoredHelpers.includes(el.label))); + completions.push(...localCandidates); } else if (isAngleComponentPath(focusPath)) { logDebugInfo('isAngleComponentPath'); // diff --git a/src/utils/ast-helpers.ts b/src/utils/ast-helpers.ts index db70e267..e2d34e2c 100644 --- a/src/utils/ast-helpers.ts +++ b/src/utils/ast-helpers.ts @@ -398,6 +398,36 @@ export function isScopedAngleTagName(path: ASTPath): boolean { return !HTML_TAGS.includes(node.tag); } +export function isAttributeValueConcatMustachePath(path: ASTPath): boolean { + const node = path.node as ASTv1.PathExpression; + + if (!isPathExpression(node)) { + return false; + } + + const parent = path.parent as ASTv1.MustacheStatement; + + if (!hasNodeType(parent, 'MustacheStatement')) { + return false; + } + + if (parent.path !== node) { + return false; + } + + const parentPath = path.parentPath; + + if (!parentPath) { + return false; + } + + if (!hasNodeType(parentPath.parent, 'ConcatStatement')) { + return false; + } + + return true; +} + export function isAngleComponentPath(path: ASTPath): boolean { const node = path.node as unknown as ASTv1.ElementNode; diff --git a/test/__snapshots__/integration-test.ts.snap b/test/__snapshots__/integration-test.ts.snap index feda89ed..4d1d8733 100644 --- a/test/__snapshots__/integration-test.ts.snap +++ b/test/__snapshots__/integration-test.ts.snap @@ -280,6 +280,75 @@ Object { } `; +exports[`integration async fs enabled: false Able to provide autocomplete information for MustacheStatements inside ConcatStatements support helper names autocomplete inside class attribute values 1`] = ` +Object { + "addonsMeta": Array [], + "registry": Object { + "component": Object { + "hello": Array [ + "app/components/hello/index.hbs", + ], + }, + "helper": Object { + "bar": Array [ + "app/helpers/bar.js", + ], + "baz": Array [ + "app/helpers/baz.ts", + ], + }, + }, + "response": Array [ + Object { + "data": Object { + "files": Array [ + "app/helpers/bar.js", + ], + }, + "detail": "helper", + "kind": 3, + "label": "bar", + "textEdit": Object { + "newText": "bar", + "range": Object { + "end": Object { + "character": 19, + "line": 0, + }, + "start": Object { + "character": 18, + "line": 0, + }, + }, + }, + }, + Object { + "data": Object { + "files": Array [ + "app/helpers/baz.ts", + ], + }, + "detail": "helper", + "kind": 3, + "label": "baz", + "textEdit": Object { + "newText": "baz", + "range": Object { + "end": Object { + "character": 19, + "line": 0, + }, + "start": Object { + "character": 18, + "line": 0, + }, + }, + }, + }, + ], +} +`; + exports[`integration async fs enabled: false Able to provide autocomplete information for angle component arguments names support template-only collocated components arguments extraction 1`] = ` Object { "addonsMeta": Array [], @@ -4206,6 +4275,75 @@ Object { } `; +exports[`integration async fs enabled: true Able to provide autocomplete information for MustacheStatements inside ConcatStatements support helper names autocomplete inside class attribute values 1`] = ` +Object { + "addonsMeta": Array [], + "registry": Object { + "component": Object { + "hello": Array [ + "app/components/hello/index.hbs", + ], + }, + "helper": Object { + "bar": Array [ + "app/helpers/bar.js", + ], + "baz": Array [ + "app/helpers/baz.ts", + ], + }, + }, + "response": Array [ + Object { + "data": Object { + "files": Array [ + "app/helpers/bar.js", + ], + }, + "detail": "helper", + "kind": 3, + "label": "bar", + "textEdit": Object { + "newText": "bar", + "range": Object { + "end": Object { + "character": 19, + "line": 0, + }, + "start": Object { + "character": 18, + "line": 0, + }, + }, + }, + }, + Object { + "data": Object { + "files": Array [ + "app/helpers/baz.ts", + ], + }, + "detail": "helper", + "kind": 3, + "label": "baz", + "textEdit": Object { + "newText": "baz", + "range": Object { + "end": Object { + "character": 19, + "line": 0, + }, + "start": Object { + "character": 18, + "line": 0, + }, + }, + }, + }, + ], +} +`; + exports[`integration async fs enabled: true Able to provide autocomplete information for angle component arguments names support template-only collocated components arguments extraction 1`] = ` Object { "addonsMeta": Array [], diff --git a/test/integration-test.ts b/test/integration-test.ts index 321778d6..e0ec3f8e 100644 --- a/test/integration-test.ts +++ b/test/integration-test.ts @@ -1629,6 +1629,32 @@ describe('integration', function () { }); }); + describe('Able to provide autocomplete information for MustacheStatements inside ConcatStatements', () => { + it('support helper names autocomplete inside class attribute values', async () => { + const result = await getResult( + CompletionRequest.method, + connection, + { + app: { + helpers: { + 'bar.js': '', + 'baz.ts': '', + }, + components: { + hello: { + 'index.hbs': '
a
', + }, + }, + }, + }, + 'app/components/hello/index.hbs', + { line: 0, character: 19 } + ); + + expect(result).toMatchSnapshot(); + }); + }); + describe('Able to provide autocomplete information for local context access', () => { it('support collocated components', async () => { const result = await getResult(