diff --git a/packages/core/__tests__/language-server/diagnostics.test.ts b/packages/core/__tests__/language-server/diagnostics.test.ts index a53fe1d9a..aba4ddbff 100644 --- a/packages/core/__tests__/language-server/diagnostics.test.ts +++ b/packages/core/__tests__/language-server/diagnostics.test.ts @@ -384,7 +384,7 @@ describe('Language Server: Diagnostics (ts plugin)', () => { "code": 2554, "end": { "line": 15, - "offset": 17, + "offset": 14, }, "relatedInformation": [ { @@ -406,7 +406,7 @@ describe('Language Server: Diagnostics (ts plugin)', () => { ], "start": { "line": 15, - "offset": 5, + "offset": 6, }, "text": "Expected 1 arguments, but got 0.", }, diff --git a/packages/core/__tests__/transform/template-to-typescript.test.ts b/packages/core/__tests__/transform/template-to-typescript.test.ts index 47b6a0287..b86681dbf 100644 --- a/packages/core/__tests__/transform/template-to-typescript.test.ts +++ b/packages/core/__tests__/transform/template-to-typescript.test.ts @@ -821,6 +821,9 @@ describe('Transform: rewriteTemplate', () => { expect(templateBody(template, { globals: [] })).toMatchInlineSnapshot(` "{ const __glintY__ = __glintDSL__.emitElement("div"); + __glintDSL__.applyAttributes(__glintY__.element, { + + }); __glintDSL__.applyModifier(__glintDSL__.resolve(modifier)(__glintY__.element, { foo: "bar" , ...__glintDSL__.NamedArgsMarker })); }" `); @@ -832,6 +835,9 @@ describe('Transform: rewriteTemplate', () => { expect(templateBody(template, { globals: [] })).toMatchInlineSnapshot(` "{ const __glintY__ = __glintDSL__.emitComponent(__glintDSL__.resolve(MyComponent)()); + __glintDSL__.applyAttributes(__glintY__.element, { + + }); __glintDSL__.applyModifier(__glintDSL__.resolve(modifier)(__glintY__.element, { foo: "bar" , ...__glintDSL__.NamedArgsMarker })); }" `); @@ -973,6 +979,9 @@ describe('Transform: rewriteTemplate', () => { "{ const __glintY__ = __glintDSL__.emitElement("div"); __glintDSL__.applySplattributes(__glintRef__.element, __glintY__.element); + __glintDSL__.applyAttributes(__glintY__.element, { + + }); }" `); }); @@ -1017,6 +1026,9 @@ describe('Transform: rewriteTemplate', () => { "{ const __glintY__ = __glintDSL__.emitComponent(__glintDSL__.resolve(Foo)()); __glintDSL__.applySplattributes(__glintRef__.element, __glintY__.element); + __glintDSL__.applyAttributes(__glintY__.element, { + + }); }" `); }); diff --git a/packages/core/package.json b/packages/core/package.json index 56495f281..2a836699b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -34,6 +34,7 @@ "test:tsc": "echo 'no standalone typecheck within this project'", "test:watch": "vitest watch", "build": "tsc --build", + "dev": "tsc --watch", "prepack": "pnpm build" }, "peerDependencies": { diff --git a/packages/core/src/transform/diagnostics/augmentation.ts b/packages/core/src/transform/diagnostics/augmentation.ts index e0dbdebc0..d6674a9ed 100644 --- a/packages/core/src/transform/diagnostics/augmentation.ts +++ b/packages/core/src/transform/diagnostics/augmentation.ts @@ -235,7 +235,7 @@ function checkImplicitAnyError( // This error may appear either on `` or `{{foo}}`/`(foo)` let globalName = sourceNode.type === 'ElementNode' - ? sourceNode.tag.split('.')[0] + ? sourceNode.path.head.original : sourceNode.type === 'PathExpression' && sourceNode.head.type === 'VarHead' ? sourceNode.head.name : null; diff --git a/packages/core/src/transform/template/map-template-contents.ts b/packages/core/src/transform/template/map-template-contents.ts index 5d44fea57..bf75a23c0 100644 --- a/packages/core/src/transform/template/map-template-contents.ts +++ b/packages/core/src/transform/template/map-template-contents.ts @@ -27,7 +27,7 @@ export type Mapper = { * Given a @glimmer/syntax AST node, returns the corresponding start * and end offsets of that node in the original source. */ - rangeForNode: (node: AST.Node) => Range; + rangeForNode: (node: AST.Node, span?: AST.Node['loc']) => Range; /** * Captures the existence of a directive specified by the given source @@ -81,6 +81,12 @@ export type Mapper = { * corresponding to the given AST node in the original source. */ forNode(node: AST.Node, callback: () => void, codeFeaturesForNode?: CodeInformation): void; + forNodeWithSpan( + node: AST.Node, + span: AST.Node['loc'], + callback: () => void, + codeFeaturesForNode?: CodeInformation, + ): void; /** * This needs to be called after any node that "consumes" a `glint-expect-error` directive. @@ -311,6 +317,15 @@ export function mapTemplateContents( captureMapping(mapper.rangeForNode(node), node, false, callback, codeFeaturesForNode); }, + forNodeWithSpan( + node: AST.Node, + span: AST.Node['loc'], + callback: () => void, + codeFeaturesForNode?: CodeInformation, + ) { + captureMapping(mapper.rangeForNode(node, span), node, false, callback, codeFeaturesForNode); + }, + error(message: string, location: Range) { errors.push({ message, location }); }, @@ -460,9 +475,14 @@ function calculateLineOffsets(template: string, contentOffset: number): Array): (node: AST.Node) => Range { - return (node) => { +function buildRangeForNode( + offsets: Array, +): (node: AST.Node, span?: AST.Node['loc']) => Range { + return (node, span) => { let { loc } = node; + if (span) { + loc = span; + } let start = offsets[loc.start.line] + loc.start.column; let end = offsets[loc.end.line] + loc.end.column; diff --git a/packages/core/src/transform/template/template-to-typescript.ts b/packages/core/src/transform/template/template-to-typescript.ts index 3c7956714..2060f8623 100644 --- a/packages/core/src/transform/template/template-to-typescript.ts +++ b/packages/core/src/transform/template/template-to-typescript.ts @@ -695,7 +695,7 @@ export function templateToTypescript( mapper.text('const __glintY__ = __glintDSL__.emitComponent('); // Error boundary: "Expected 1 arguments, but got 0." e.g. when invoking `` - mapper.forNode(node, () => { + mapper.forNode(node.path, () => { mapper.text('__glintDSL__.resolve('); emitPathContents(path, start, kind); mapper.text(')'); @@ -707,7 +707,7 @@ export function templateToTypescript( let dataAttrs = node.attributes.filter(({ name }) => name.startsWith('@')); if (dataAttrs.length) { // Error boundary: "Expected 0 arguments, but got 1." e.g. when invoking `` - mapper.forNode(node, () => { + mapper.forNodeWithSpan(node, node.openTag, () => { mapper.text('{ '); for (let attr of dataAttrs) { @@ -883,7 +883,9 @@ export function templateToTypescript( mapper.indent(); mapper.text('const __glintY__ = __glintDSL__.emitElement('); - mapper.text(JSON.stringify(node.tag)); + mapper.forNode(node.path, () => { + mapper.text(JSON.stringify(node.tag)); + }); mapper.text(');'); mapper.newline(); @@ -930,39 +932,46 @@ export function templateToTypescript( (attr) => !attr.name.startsWith('@') && attr.name !== SPLATTRIBUTES, ); - if (!attributes.length) return; - mapper.text('__glintDSL__.applyAttributes(__glintY__.element, {'); - mapper.newline(); - mapper.indent(); - let start = template.indexOf(node.tag, rangeForNode(node).start) + node.tag.length; + mapper.forNodeWithSpan(node, node.openTag, () => { + mapper.newline(); + mapper.indent(); - for (let attr of attributes) { - mapper.forNode(attr, () => { - start = template.indexOf(attr.name, start + 1); + let start = template.indexOf(node.tag, rangeForNode(node).start) + node.tag.length; - emitHashKey(attr.name, start); - mapper.text(': '); + for (let attr of attributes) { + mapper.forNode(attr, () => { + start = template.indexOf(attr.name, start + 1); - if (attr.value.type === 'MustacheStatement') { - emitMustacheStatement(attr.value, 'attr'); - } else if (attr.value.type === 'ConcatStatement') { - emitConcatStatement(attr.value); - } else { - mapper.text(JSON.stringify(attr.value.chars)); - } + emitHashKey(attr.name, start); + mapper.text(': '); - mapper.text(','); - mapper.newline(); - }); + if (attr.value.type === 'MustacheStatement') { + emitMustacheStatement(attr.value, 'attr'); + } else if (attr.value.type === 'ConcatStatement') { + emitConcatStatement(attr.value); + } else { + mapper.text(JSON.stringify(attr.value.chars)); + } - mapper.terminateDirectiveAreaOfEffect('emitPlainAttributes'); - } + mapper.text(','); + mapper.newline(); + }); - mapper.dedent(); - mapper.text('});'); - mapper.newline(); + mapper.terminateDirectiveAreaOfEffect('emitPlainAttributes'); + } + + // in case there are no attributes, this would allow completions to trigger + if (attributes.length === 0) { + mapper.text(' '); + mapper.newline(); + } + + mapper.dedent(); + mapper.text('});'); + mapper.newline(); + }); } function emitSplattributes(node: AST.ElementNode): void {