diff --git a/packages/core/src/transform/diagnostics/augmentation.ts b/packages/core/src/transform/diagnostics/augmentation.ts index d6674a9ed..97228a457 100644 --- a/packages/core/src/transform/diagnostics/augmentation.ts +++ b/packages/core/src/transform/diagnostics/augmentation.ts @@ -18,10 +18,8 @@ export function augmentDiagnostics( const start = diagnostic.start; const end = start + diagnostic.length; - // TODO: de-hardwire "disregard.gts" and consider the case of two-file components where - // the hardcoded source file name might be the hbs file. const rangeWithMappingAndSource = transformedModule.getTransformedRange( - 'disregard.gts', + transformedModule.originalFileName, start, end, ); diff --git a/packages/core/src/transform/template/rewrite-module.ts b/packages/core/src/transform/template/rewrite-module.ts index 56684931e..a44c8f14c 100644 --- a/packages/core/src/transform/template/rewrite-module.ts +++ b/packages/core/src/transform/template/rewrite-module.ts @@ -33,7 +33,7 @@ export type RewriteInput = { script: SourceFile; template?: SourceFile }; // // 1. It doesn't break Organize Imports command // 2. It doesn't introduce any keywords/variables that'll show up in auto-complete suggestions -const EXTENSION_FIXING_HEADER_HACK = ` +const EXTENSION_FIXING_HEADER_HACK_GTS = ` // @ts-expect-error ({} as typeof import('./__glint-hacky-nonexistent.gts')); @@ -42,6 +42,15 @@ const EXTENSION_FIXING_HEADER_HACK = ` `; +const EXTENSION_FIXING_HEADER_HACK_GJS = ` +// @ts-expect-error +(/** @type {typeof import("./__glint-hacky-nonexistent.gts")} */ ({})) + +// @ts-expect-error +(/** @type {typeof import("./__glint-hacky-nonexistent.gjs")} */ ({})) + +`; + /** * Given the script and/or template that together comprise a component module, * returns a `TransformedModule` representing the combined result, with the @@ -70,7 +79,7 @@ export function rewriteModule( let sparseSpans = completeCorrelatedSpans(partialSpans); let { contents, correlatedSpans } = calculateTransformedSource(script, sparseSpans); - return new TransformedModule(contents, errors, directives, correlatedSpans); + return new TransformedModule(contents, errors, directives, correlatedSpans, script.filename); } /** @@ -94,7 +103,9 @@ function calculateCorrelatedSpans( originalStart: 0, originalLength: 0, insertionPoint: 0, - transformedSource: EXTENSION_FIXING_HEADER_HACK, + transformedSource: environment.isUntypedScript(script.filename) + ? EXTENSION_FIXING_HEADER_HACK_GJS + : EXTENSION_FIXING_HEADER_HACK_GTS, }, ]; diff --git a/packages/core/src/transform/template/transformed-module.ts b/packages/core/src/transform/template/transformed-module.ts index 9c89a7b51..dfa68a341 100644 --- a/packages/core/src/transform/template/transformed-module.ts +++ b/packages/core/src/transform/template/transformed-module.ts @@ -68,6 +68,7 @@ export default class TransformedModule { public readonly errors: ReadonlyArray, public readonly directives: ReadonlyArray, public readonly correlatedSpans: Array, + public readonly originalFileName: string, ) {} public toDebugString(): string { diff --git a/packages/core/src/volar/gts-virtual-code.ts b/packages/core/src/volar/gts-virtual-code.ts index 2fa4c049f..0c7e01428 100644 --- a/packages/core/src/volar/gts-virtual-code.ts +++ b/packages/core/src/volar/gts-virtual-code.ts @@ -129,8 +129,11 @@ export class VirtualGtsCode implements VirtualCode { }; const contents = snapshot.getText(0, length); - - let script = { filename: 'disregard.gts', contents }; + const isJavascript = + this.languageId === 'glimmer-js' || this.languageId === 'javascript.glimmer'; + const embeddedLanguageId = isJavascript ? 'javascript' : 'typescript'; + const filename = isJavascript ? 'root.gjs' : 'root.gts'; + let script = { filename, contents }; let template = undefined; const transformedModule = rewriteModule( @@ -149,7 +152,7 @@ export class VirtualGtsCode implements VirtualCode { { embeddedCodes: [], id: 'ts', - languageId: 'typescript', + languageId: embeddedLanguageId, mappings, snapshot: new ScriptSnapshot(transformedModule.transformedContents), directives: transformedModule.directives, @@ -213,7 +216,7 @@ export class VirtualGtsCode implements VirtualCode { { embeddedCodes: [], id: 'ts', - languageId: 'typescript', + languageId: embeddedLanguageId, mappings: [ { // Hacked hardwired values for now. diff --git a/packages/tsserver-plugin/src/typescript-server-plugin.ts b/packages/tsserver-plugin/src/typescript-server-plugin.ts index ae21689fe..e51c34d2a 100644 --- a/packages/tsserver-plugin/src/typescript-server-plugin.ts +++ b/packages/tsserver-plugin/src/typescript-server-plugin.ts @@ -144,7 +144,7 @@ function getCompletionsAtPosition( const transformedModule: TransformedModule = root?.transformedModule; const completions = getCompletionsAtPosition(fileName, position, options, formattingSettings); const transformedRange = transformedModule?.getTransformedRange( - 'disregard.gts', + transformedModule.originalFileName, position, position, ); diff --git a/packages/vscode/__fixtures__/ember-app-loose-and-gts/app/components/GreetingUntyped.gjs b/packages/vscode/__fixtures__/ember-app-loose-and-gts/app/components/GreetingUntyped.gjs new file mode 100644 index 000000000..85bdee069 --- /dev/null +++ b/packages/vscode/__fixtures__/ember-app-loose-and-gts/app/components/GreetingUntyped.gjs @@ -0,0 +1,9 @@ +import Component from '@glimmer/component'; + +export default class Greeting extends Component { + message = 'Hello'; + + +} diff --git a/packages/vscode/__fixtures__/ember-app-loose-and-gts/app/components/OtherUntyped.gjs b/packages/vscode/__fixtures__/ember-app-loose-and-gts/app/components/OtherUntyped.gjs new file mode 100644 index 000000000..5e142a9eb --- /dev/null +++ b/packages/vscode/__fixtures__/ember-app-loose-and-gts/app/components/OtherUntyped.gjs @@ -0,0 +1,12 @@ +import Component from '@glimmer/component'; +import Colocated from './colocated-folder'; +import Greeting from './GreetingUntyped'; + +export default class Other extends Component { + message = 'Hello'; + + +} diff --git a/packages/vscode/__fixtures__/ember-app-loose-and-gts/tsconfig.json b/packages/vscode/__fixtures__/ember-app-loose-and-gts/tsconfig.json index ebe684ff2..e91071f63 100644 --- a/packages/vscode/__fixtures__/ember-app-loose-and-gts/tsconfig.json +++ b/packages/vscode/__fixtures__/ember-app-loose-and-gts/tsconfig.json @@ -4,7 +4,8 @@ "target": "es2019", "module": "es2015", "moduleResolution": "bundler", - "skipLibCheck": true + "skipLibCheck": true, + "checkJs": true }, "glint": { "environment": ["ember-loose", "ember-template-imports"] diff --git a/packages/vscode/__tests__/helpers/async.ts b/packages/vscode/__tests__/helpers/async.ts index bd4f7d607..fc22a1b60 100644 --- a/packages/vscode/__tests__/helpers/async.ts +++ b/packages/vscode/__tests__/helpers/async.ts @@ -2,7 +2,7 @@ export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -export async function waitUntil(callback: () => unknown): Promise { +export async function waitUntil(callback: () => unknown, message?: string): Promise { let start = Date.now(); while (Date.now() - start < 15_000) { if (await callback()) { @@ -12,5 +12,5 @@ export async function waitUntil(callback: () => unknown): Promise { await sleep(500); } - throw new Error(`waitUntil condition never came true`); + throw new Error(`waitUntil condition never came true (${message})`); } diff --git a/packages/vscode/__tests__/ts-plugin-tests/smoketest-ember-app-loose-and-gts.test.ts b/packages/vscode/__tests__/ts-plugin-tests/smoketest-ember-app-loose-and-gts.test.ts index 46992c39a..15828d4f6 100644 --- a/packages/vscode/__tests__/ts-plugin-tests/smoketest-ember-app-loose-and-gts.test.ts +++ b/packages/vscode/__tests__/ts-plugin-tests/smoketest-ember-app-loose-and-gts.test.ts @@ -34,7 +34,7 @@ describe('Smoke test: Loose Mode + GTS with TS Plugin Mode', () => { let scriptEditor = await window.showTextDocument(scriptURI, { viewColumn: ViewColumn.One }); // Wait for a diagnostic to appear in the template - await waitUntil(() => languages.getDiagnostics(scriptURI).length); + await waitUntil(() => languages.getDiagnostics(scriptURI).length, 'diagnostic to appear'); expect(languages.getDiagnostics(scriptURI)).toMatchObject([ { @@ -51,7 +51,10 @@ describe('Smoke test: Loose Mode + GTS with TS Plugin Mode', () => { }); // Wait for the diagnostic to disappear - await waitUntil(() => languages.getDiagnostics(scriptURI).length == 0); + await waitUntil( + () => languages.getDiagnostics(scriptURI).length == 0, + 'diagnostic to disappear', + ); }); }); }); diff --git a/test-packages/package-test-core/__tests__/language-server/document-symbols.test.ts b/test-packages/package-test-core/__tests__/language-server/document-symbols.test.ts index 87bccfca6..6b0c99c89 100644 --- a/test-packages/package-test-core/__tests__/language-server/document-symbols.test.ts +++ b/test-packages/package-test-core/__tests__/language-server/document-symbols.test.ts @@ -11,7 +11,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; describe('Language Server: Document Symbols (language server)', () => { afterEach(teardownSharedTestWorkspaceAfterEach); - test('component invocation', async () => { + test('typed gts file', async () => { const document = await prepareDocument( 'ts-template-imports-app/src/empty-fixture.gts', 'glimmer-ts', @@ -108,6 +108,104 @@ describe('Language Server: Document Symbols (language server)', () => { ] `); }); + + test('untyped gjs file', async () => { + const document = await prepareDocument( + 'ts-template-imports-app/src/empty-fixture-untyped.gjs', + 'glimmer-js', + stripIndent` + import Component from '@glimmer/component'; + import Greeting from './Greeting.gts'; + + export default class Application extends Component { + + } + `, + ); + + const symbols = await performDocumentSymbolsRequest(document); + + expect(symbols).toMatchInlineSnapshot(` + [ + { + "children": [ + { + "kind": 8, + "name": "Greeting", + "range": { + "end": { + "character": 33, + "line": 5, + }, + "start": { + "character": 4, + "line": 5, + }, + }, + "selectionRange": { + "end": { + "character": 33, + "line": 5, + }, + "start": { + "character": 4, + "line": 5, + }, + }, + }, + { + "kind": 2, + "name": "template", + "range": { + "end": { + "character": 13, + "line": 6, + }, + "start": { + "character": 2, + "line": 4, + }, + }, + "selectionRange": { + "end": { + "character": 13, + "line": 6, + }, + "start": { + "character": 2, + "line": 4, + }, + }, + }, + ], + "kind": 5, + "name": "Application", + "range": { + "end": { + "character": 1, + "line": 7, + }, + "start": { + "character": 0, + "line": 3, + }, + }, + "selectionRange": { + "end": { + "character": 32, + "line": 3, + }, + "start": { + "character": 21, + "line": 3, + }, + }, + }, + ] + `); + }); }); async function performDocumentSymbolsRequest(document: TextDocument): Promise { diff --git a/test-packages/package-test-core/__tests__/transform/rewrite.test.ts b/test-packages/package-test-core/__tests__/transform/rewrite.test.ts index 37f9d1010..d8ce5b7d9 100644 --- a/test-packages/package-test-core/__tests__/transform/rewrite.test.ts +++ b/test-packages/package-test-core/__tests__/transform/rewrite.test.ts @@ -421,10 +421,10 @@ describe('Transform: rewriteModule', () => { expect(transformedModule?.transformedContents).toMatchInlineSnapshot(` " // @ts-expect-error - ({} as typeof import('./__glint-hacky-nonexistent.gts')); + (/** @type {typeof import("./__glint-hacky-nonexistent.gts")} */ ({})) // @ts-expect-error - ({} as typeof import('./__glint-hacky-nonexistent.gjs')); + (/** @type {typeof import("./__glint-hacky-nonexistent.gjs")} */ ({})) import templateOnly from '@glimmer/component/template-only'; diff --git a/test-packages/ts-template-imports-app/src/empty-fixture-untyped.gjs b/test-packages/ts-template-imports-app/src/empty-fixture-untyped.gjs new file mode 100644 index 000000000..b36cfee1f --- /dev/null +++ b/test-packages/ts-template-imports-app/src/empty-fixture-untyped.gjs @@ -0,0 +1,6 @@ +// This file is intentionally empty. +// It exists as a hack to get around some issues when testing our TS Plugin +// within tsserver harness where, even though many of our tests are only +// updating tsserver's in-memory content for a file, tsserver still needs +// the file to exist in the file system in order for things like References +// and other language features to work properly.