v{{@version}}
.
+ \`;
+ }
+ `;
+
+ let componentB = stripIndent`
+ import Component, { hbs } from '@glint/environment-glimmerx/component';
+
+ export default class ComponentB extends Component {
+ public startupTime = new Date().toISOString();
+
+ public static template = hbs\`
+ {{! @glint-ignore: this looks like a typo but for some reason it isn't }}
+ The current time is {{this.startupTimee}}.
+ \`;
+ }
+ `;
+
+ project.write('component-a.ts', componentA);
+ project.write('component-b.ts', componentB);
+
+ let server = project.startLanguageServer();
+
+ expect(server.getDiagnostics(project.fileURI('component-a.ts'))).toEqual([]);
+ expect(server.getDiagnostics(project.fileURI('component-b.ts'))).toEqual([]);
+
+ server.openFile(project.fileURI('component-a.ts'), componentA);
+ server.updateFile(
+ project.fileURI('component-a.ts'),
+ componentA.replace('{{! @glint-expect-error }}', '')
+ );
+
+ expect(server.getDiagnostics(project.fileURI('component-b.ts'))).toEqual([]);
+ expect(server.getDiagnostics(project.fileURI('component-a.ts'))).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "message": "Property 'version' does not exist on type 'EmptyObject'.",
+ "range": Object {
+ "end": Object {
+ "character": 36,
+ "line": 5,
+ },
+ "start": Object {
+ "character": 29,
+ "line": 5,
+ },
+ },
+ "severity": 1,
+ "source": "glint:ts(2339)",
+ "tags": Array [],
+ },
+ ]
+ `);
+
+ server.updateFile(project.fileURI('component-a.ts'), componentA);
+
+ expect(server.getDiagnostics(project.fileURI('component-a.ts'))).toEqual([]);
+ expect(server.getDiagnostics(project.fileURI('component-b.ts'))).toEqual([]);
+
+ server.updateFile(project.fileURI('component-a.ts'), componentA.replace('{{@version}}', ''));
+
+ expect(server.getDiagnostics(project.fileURI('component-b.ts'))).toEqual([]);
+ expect(server.getDiagnostics(project.fileURI('component-a.ts'))).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "message": "Unused '@glint-expect-error' directive.",
+ "range": Object {
+ "end": Object {
+ "character": 30,
+ "line": 4,
+ },
+ "start": Object {
+ "character": 4,
+ "line": 4,
+ },
+ },
+ "severity": 1,
+ "source": "glint",
+ "tags": Array [],
+ },
+ ]
+ `);
+ });
});
diff --git a/packages/core/src/cli/diagnostics.ts b/packages/core/src/cli/diagnostics.ts
new file mode 100644
index 000000000..effb0168d
--- /dev/null
+++ b/packages/core/src/cli/diagnostics.ts
@@ -0,0 +1,14 @@
+import type ts from 'typescript';
+
+export function buildDiagnosticFormatter(
+ ts: typeof import('typescript')
+): (diagnostic: ts.Diagnostic) => string {
+ const formatDiagnosticHost: ts.FormatDiagnosticsHost = {
+ getCanonicalFileName: (name) => name,
+ getCurrentDirectory: ts.sys.getCurrentDirectory,
+ getNewLine: () => ts.sys.newLine,
+ };
+
+ return (diagnostic) =>
+ ts.formatDiagnosticsWithColorAndContext([diagnostic], formatDiagnosticHost);
+}
diff --git a/packages/core/src/cli/perform-check.ts b/packages/core/src/cli/perform-check.ts
index bda03fead..15a979b00 100644
--- a/packages/core/src/cli/perform-check.ts
+++ b/packages/core/src/cli/perform-check.ts
@@ -1,6 +1,7 @@
import type ts from 'typescript';
import TransformManager from '../common/transform-manager';
import { GlintConfig } from '@glint/config';
+import { buildDiagnosticFormatter } from './diagnostics';
export function performCheck(
ts: typeof import('typescript'),
@@ -12,6 +13,7 @@ export function performCheck(
let transformManager = new TransformManager(ts, glintConfig);
let parsedConfig = loadTsconfig(ts, configPath, optionsToExtend);
let compilerHost = createCompilerHost(ts, parsedConfig.options, transformManager);
+ let formatDiagnostic = buildDiagnosticFormatter(ts);
let program = ts.createProgram({
rootNames: rootNames.length ? rootNames : parsedConfig.fileNames,
options: parsedConfig.options,
@@ -20,12 +22,13 @@ export function performCheck(
program.emit();
- let diagnostics = collectDiagnostics(program, transformManager, parsedConfig.options);
- for (let diagnostic of diagnostics) {
- console.error(transformManager.formatDiagnostic(diagnostic));
+ let baselineDiagnostics = collectDiagnostics(program, transformManager, parsedConfig.options);
+ let fullDiagnostics = transformManager.rewriteDiagnostics(baselineDiagnostics);
+ for (let diagnostic of fullDiagnostics) {
+ console.error(formatDiagnostic(diagnostic));
}
- process.exit(diagnostics.length ? 1 : 0);
+ process.exit(fullDiagnostics.length ? 1 : 0);
}
function collectDiagnostics(
diff --git a/packages/core/src/cli/perform-watch.ts b/packages/core/src/cli/perform-watch.ts
index 3f03022d1..33c68c90e 100644
--- a/packages/core/src/cli/perform-watch.ts
+++ b/packages/core/src/cli/perform-watch.ts
@@ -1,5 +1,6 @@
import TransformManager from '../common/transform-manager';
import { GlintConfig } from '@glint/config';
+import { buildDiagnosticFormatter } from './diagnostics';
export function performWatch(
ts: typeof import('typescript'),
@@ -8,12 +9,13 @@ export function performWatch(
optionsToExtend: import('typescript').CompilerOptions
): void {
let transformManager = new TransformManager(ts, glintConfig);
+ let formatDiagnostic = buildDiagnosticFormatter(ts);
let host = ts.createWatchCompilerHost(
tsconfigPath ?? 'tsconfig.json',
optionsToExtend,
sysForWatchCompilerHost(ts, transformManager),
ts.createSemanticDiagnosticsBuilderProgram,
- (diagnostic) => console.error(transformManager.formatDiagnostic(diagnostic))
+ (diagnostic) => console.error(formatDiagnostic(diagnostic))
);
patchWatchCompilerHost(host, transformManager);
@@ -44,10 +46,16 @@ function patchWatchCompilerHost(host: WatchCompilerHost, transformManager: Trans
}
function patchProgram(program: Program, transformManager: TransformManager): void {
- let { getSyntacticDiagnostics } = program;
+ let { getSyntacticDiagnostics, getSemanticDiagnostics } = program;
+
program.getSyntacticDiagnostics = function (sourceFile, cancelationToken) {
let diagnostics = getSyntacticDiagnostics.call(program, sourceFile, cancelationToken);
let transformDiagnostics = transformManager.getTransformDiagnostics(sourceFile?.fileName);
return [...diagnostics, ...transformDiagnostics];
};
+
+ program.getSemanticDiagnostics = (sourceFile, cancellationToken) => {
+ let diagnostics = getSemanticDiagnostics.call(program, sourceFile, cancellationToken);
+ return transformManager.rewriteDiagnostics(diagnostics, sourceFile?.fileName);
+ };
}
diff --git a/packages/core/src/common/transform-manager.ts b/packages/core/src/common/transform-manager.ts
index 3608fd993..ef453fb59 100644
--- a/packages/core/src/common/transform-manager.ts
+++ b/packages/core/src/common/transform-manager.ts
@@ -1,4 +1,11 @@
-import { TransformedModule, rewriteModule, rewriteDiagnostic } from '@glint/transform';
+import {
+ TransformedModule,
+ rewriteModule,
+ rewriteDiagnostic,
+ Directive,
+ SourceFile,
+ Range,
+} from '@glint/transform';
import type ts from 'typescript';
import { GlintConfig } from '@glint/config';
import { assert } from '@glint/transform/lib/util';
@@ -22,31 +29,46 @@ export default class TransformManager {
public getTransformDiagnostics(fileName?: string): ArrayHello, {{@foo}}! + + {{! @glint-expect-error: no @bar arg }} + {{@bar}}
\`; } @@ -79,16 +82,16 @@ describe('Debug utilities', () => { | | Mapping: Template - | hbs(0:62): hbs\`\\\\n\\\\n Hello, {{@foo}}!\\\\n
\\\\n \` - | ts(0:378): (() => {\\\\n hbs;\\\\n let χ!: typeof import(\\"@glint/environment-glimmerx/-private/dsl\\");\\\\n return χ.template(function(𝚪: import(\\"@glint/environment-glimmerx/-private/dsl\\").ResolveContext\\\\n Hello, {{@foo}}!\\\\n\\\\n {{! @glint-expect-error: no @bar arg }}\\\\n {{@bar}}\\\\n
\\\\n \` + | ts(0:433): (() => {\\\\n hbs;\\\\n let χ!: typeof import(\\"@glint/environment-glimmerx/-private/dsl\\");\\\\n return χ.template(function(𝚪: import(\\"@glint/environment-glimmerx/-private/dsl\\").ResolveContext\\\\n Hello, {{@foo}}!\\\\n
- | | ts(204:360): {\\\\n const 𝛄 = χ.emitElement(\\"p\\");\\\\n χ.applySplattributes(𝚪.element, 𝛄.element);\\\\n χ.emitValue(χ.resolveOrReturn(𝚪.args.foo)({}));\\\\n } + | | hbs(9:120):\\\\n Hello, {{@foo}}!\\\\n\\\\n {{! @glint-expect-error: no @bar arg }}\\\\n {{@bar}}\\\\n
+ | | ts(204:415): {\\\\n const 𝛄 = χ.emitElement(\\"p\\");\\\\n χ.applySplattributes(𝚪.element, 𝛄.element);\\\\n χ.emitValue(χ.resolveOrReturn(𝚪.args.foo)({}));\\\\n χ.emitValue(χ.resolveOrReturn(𝚪.args.bar)({}));\\\\n } | | | | | Mapping: AttrNode | | | hbs(12:25): ...attributes @@ -108,6 +111,24 @@ describe('Debug utilities', () => { | | | | | | | | | | | | + | | | Mapping: MustacheCommentStatement + | | | hbs(57:96): {{! @glint-expect-error: no @bar arg }} + | | | ts(354:354): + | | | + | | | Mapping: MustacheStatement + | | | hbs(103:111): {{@bar}} + | | | ts(354:407): χ.emitValue(χ.resolveOrReturn(𝚪.args.bar)({})) + | | | + | | | | Mapping: PathExpression + | | | | hbs(105:109): @bar + | | | | ts(390:401): 𝚪.args.bar + | | | | + | | | | | Mapping: Identifier + | | | | | hbs(106:109): bar + | | | | | ts(398:401): bar + | | | | | + | | | | + | | | | | |" `); diff --git a/packages/transform/__tests__/template-to-typescript.test.ts b/packages/transform/__tests__/template-to-typescript.test.ts index e46fd312a..15837b1f9 100644 --- a/packages/transform/__tests__/template-to-typescript.test.ts +++ b/packages/transform/__tests__/template-to-typescript.test.ts @@ -70,6 +70,111 @@ describe('rewriteTemplate', () => { }); }); + describe('directives', () => { + test('in a top-level mustache', () => { + let template = stripIndent` + {{! @glint-ignore: this is fine }} +