Skip to content

Commit

Permalink
Merge pull request #621 from typed-ember/explicit-gts-extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
dfreeman authored Sep 25, 2023
2 parents 5861a8b + 25a2bef commit 2c66bab
Show file tree
Hide file tree
Showing 11 changed files with 199 additions and 17 deletions.
45 changes: 45 additions & 0 deletions packages/core/__tests__/cli/custom-extensions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { stripIndent } from 'common-tags';
import stripAnsi = require('strip-ansi');
import { describe, beforeEach, afterEach, test, expect } from 'vitest';
import { Project } from 'glint-monorepo-test-utils';
import typescript from 'typescript';
import semver from 'semver';

describe('CLI: custom extensions', () => {
let project!: Project;
Expand Down Expand Up @@ -121,4 +123,47 @@ describe('CLI: custom extensions', () => {
await watch.terminate();
});
});

describe('module resolution with explicit extensions', () => {
beforeEach(() => {
project.setGlintConfig({ environment: 'ember-template-imports' });
project.write({
'index.gts': stripIndent`
import Greeting from './Greeting.gts';
<template><Greeting /></template>
`,
'Greeting.gts': stripIndent`
<template>Hello!</template>
`,
});
});

test('is illegal by default', async () => {
let result = await project.check({ reject: false });

expect(result.exitCode).toBe(1);
expect(stripAnsi(result.stderr)).toMatchInlineSnapshot(`
"index.gts:1:22 - error TS2307: Cannot find module './Greeting.gts' or its corresponding type declarations.
1 import Greeting from './Greeting.gts';
~~~~~~~~~~~~~~~~
"
`);
});

test.runIf(semver.gte(typescript.version, '5.0.0'))(
'works with `allowImportingTsExtensions: true`',
async () => {
project.updateTsconfig((config) => {
config.compilerOptions ??= {};
config.compilerOptions['allowImportingTsExtensions'] = true;
});

let result = await project.check();

expect(result.exitCode).toBe(0);
expect(result.stderr).toBe('');
}
);
});
});
57 changes: 57 additions & 0 deletions packages/core/__tests__/language-server/custom-extensions.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Project } from 'glint-monorepo-test-utils';
import { describe, beforeEach, afterEach, test, expect } from 'vitest';
import { stripIndent } from 'common-tags';
import typescript from 'typescript';
import semver from 'semver';

describe('Language Server: custom file extensions', () => {
let project!: Project;
Expand Down Expand Up @@ -254,4 +256,59 @@ describe('Language Server: custom file extensions', () => {
]);
});
});

describe('module resolution with explicit extensions', () => {
beforeEach(() => {
project.setGlintConfig({ environment: 'ember-template-imports' });
project.write({
'index.gts': stripIndent`
import Greeting from './Greeting.gts';
<template><Greeting /></template>
`,
'Greeting.gts': stripIndent`
<template>Hello!</template>
`,
});
});

test('is illegal by default', async () => {
let server = project.startLanguageServer();

expect(server.getDiagnostics(project.fileURI('index.gts'))).toMatchInlineSnapshot(`
[
{
"code": 2307,
"message": "Cannot find module './Greeting.gts' or its corresponding type declarations.",
"range": {
"end": {
"character": 37,
"line": 0,
},
"start": {
"character": 21,
"line": 0,
},
},
"severity": 1,
"source": "glint",
"tags": [],
},
]
`);
});

test.runIf(semver.gte(typescript.version, '5.0.0'))(
'works with `allowImportingTsExtensions: true`',
async () => {
project.updateTsconfig((config) => {
config.compilerOptions ??= {};
config.compilerOptions['allowImportingTsExtensions'] = true;
});

let server = project.startLanguageServer();

expect(server.getDiagnostics(project.fileURI('index.gts'))).toEqual([]);
}
);
});
});
5 changes: 4 additions & 1 deletion packages/core/src/cli/perform-build-watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function performBuildWatch(
projects: string[],
buildOptions: TS.BuildOptions
): void {
let transformManagerPool = new TransformManagerPool(ts.sys);
let transformManagerPool = new TransformManagerPool(ts);
let formatDiagnostic = buildDiagnosticFormatter(ts);
let buildProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram;

Expand All @@ -20,6 +20,9 @@ export function performBuildWatch(
(diagnostic) => console.error(formatDiagnostic(diagnostic))
);

// @ts-ignore: This hook was added in TS5, and is safely irrelevant in earlier versions. Once we drop support for 4.x, we can also remove this @ts-ignore comment.
host.resolveModuleNameLiterals = transformManagerPool.resolveModuleNameLiterals;

let builder = ts.createSolutionBuilderWithWatch(host, projects, buildOptions);
builder.build();
}
5 changes: 4 additions & 1 deletion packages/core/src/cli/perform-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface BuildOptions extends TS.BuildOptions {
}

export function performBuild(ts: TypeScript, projects: string[], buildOptions: BuildOptions): void {
let transformManagerPool = new TransformManagerPool(ts.sys);
let transformManagerPool = new TransformManagerPool(ts);
let formatDiagnostic = buildDiagnosticFormatter(ts);
let buildProgram = ts.createEmitAndSemanticDiagnosticsBuilderProgram;

Expand All @@ -23,6 +23,9 @@ export function performBuild(ts: TypeScript, projects: string[], buildOptions: B
(diagnostic) => console.error(formatDiagnostic(diagnostic))
);

// @ts-ignore: This hook was added in TS5, and is safely irrelevant in earlier versions. Once we drop support for 4.x, we can also remove this @ts-ignore comment.
host.resolveModuleNameLiterals = transformManagerPool.resolveModuleNameLiterals;

let builder = ts.createSolutionBuilder(host, projects, buildOptions);
let exitStatus = buildOptions.clean ? builder.clean() : builder.build();
process.exit(exitStatus);
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/cli/perform-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ function createCompilerHost(
? ts.createIncrementalCompilerHost(options, sysForCompilerHost(ts, transformManager))
: ts.createCompilerHost(options);

// @ts-ignore: This hook was added in TS5, and is safely irrelevant in earlier versions. Once we drop support for 4.x, we can also remove this @ts-ignore comment.
host.resolveModuleNameLiterals = transformManager.resolveModuleNameLiterals;
host.fileExists = transformManager.fileExists;
host.readFile = transformManager.readTransformedFile;
host.readDirectory = transformManager.readDirectory;
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/cli/perform-watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ export function performWatch(glintConfig: GlintConfig, optionsToExtend: ts.Compi
(diagnostic) => console.error(formatDiagnostic(diagnostic))
);

// @ts-ignore: This hook was added in TS5, and is safely irrelevant in earlier versions. Once we drop support for 4.x, we can also remove this @ts-ignore comment.
host.resolveModuleNameLiterals = transformManager.resolveModuleNameLiterals;

ts.createWatchProgram(host);
}
34 changes: 32 additions & 2 deletions packages/core/src/cli/utils/transform-manager-pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { assert } from './assert.js';
* config.
*/
export default class TransformManagerPool {
#rootTS: typeof TS;
#rootSys: TS.System;
#managers = new Map<GlintConfig, TransformManager>();
#loader = new ConfigLoader();
Expand All @@ -25,8 +26,9 @@ export default class TransformManagerPool {
return true;
}

constructor(sys: TS.System) {
this.#rootSys = sys;
constructor(ts: typeof TS) {
this.#rootTS = ts;
this.#rootSys = ts.sys;
}

public managerForFile(path: string): TransformManager | null {
Expand All @@ -45,6 +47,34 @@ export default class TransformManagerPool {
return manager;
}

public resolveModuleNameLiterals = (
moduleLiterals: readonly TS.StringLiteralLike[],
containingFile: string,
redirectedReference: TS.ResolvedProjectReference | undefined,
options: TS.CompilerOptions
): readonly TS.ResolvedModuleWithFailedLookupLocations[] => {
let resolveModuleNameLiterals = this.managerForFile(containingFile)?.resolveModuleNameLiterals;
if (resolveModuleNameLiterals) {
return resolveModuleNameLiterals(
moduleLiterals,
containingFile,
redirectedReference,
options
);
} else {
return moduleLiterals.map((literal) =>
this.#rootTS.resolveModuleName(
literal.text,
containingFile,
options,
this.#rootSys,
undefined,
redirectedReference
)
);
}
};

public readDirectory = (
rootDir: string,
extensions: ReadonlyArray<string>,
Expand Down
45 changes: 44 additions & 1 deletion packages/core/src/common/transform-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,25 @@ type TransformInfo = {

export default class TransformManager {
private transformCache = new Map<string, TransformInfo>();
private moduleResolutionHost: ts.ModuleResolutionHost;
private readonly ts: typeof import('typescript');

public readonly moduleResolutionCache: ts.ModuleResolutionCache;

constructor(
private glintConfig: GlintConfig,
private documents: DocumentCache = new DocumentCache(glintConfig)
) {
this.ts = glintConfig.ts;
this.moduleResolutionCache = this.ts.createModuleResolutionCache(
this.ts.sys.getCurrentDirectory(),
(name) => name
);
this.moduleResolutionHost = {
...this.ts.sys,
readFile: this.readTransformedFile,
fileExists: this.fileExists,
};
}

public getTransformDiagnostics(fileName?: string): Array<Diagnostic> {
Expand Down Expand Up @@ -161,6 +173,32 @@ export default class TransformManager {
return { transformedFileName, transformedOffset };
}

public resolveModuleNameLiterals = (
moduleLiterals: readonly ts.StringLiteralLike[],
containingFile: string,
redirectedReference: ts.ResolvedProjectReference | undefined,
options: ts.CompilerOptions
): readonly ts.ResolvedModuleWithFailedLookupLocations[] => {
return moduleLiterals.map((literal) => {
// If import paths are allowed to include TS extensions (`.ts`, `.tsx`, etc), then we want to
// ensure we normalize things like `.gts` to the standard script path we present elsewhere so
// that TS understands the intent.
// @ts-ignore: this flag isn't available in the oldest versions of TS we support
let scriptPath = options.allowImportingTsExtensions
? this.getScriptPathForTS(literal.text)
: literal.text;

return this.ts.resolveModuleName(
scriptPath,
containingFile,
options,
this.moduleResolutionHost,
this.moduleResolutionCache,
redirectedReference
);
});
};

public watchTransformedFile = (
path: string,
originalCallback: ts.FileWatcherCallback,
Expand All @@ -175,6 +213,8 @@ export default class TransformManager {
let { glintConfig, documents } = this;
let callback: ts.FileWatcherCallback = (watchedPath, eventKind) => {
if (eventKind === this.ts.FileWatcherEventKind.Deleted) {
// Adding or removing a file invalidates most of what we think we know about module resolution
this.moduleResolutionCache.clear();
this.documents.removeDocument(watchedPath);
} else {
this.documents.markDocumentStale(watchedPath);
Expand Down Expand Up @@ -213,8 +253,11 @@ export default class TransformManager {
throw new Error('Internal error: TS `watchDirectory` unavailable');
}

let callback: ts.DirectoryWatcherCallback = (filename) =>
let callback: ts.DirectoryWatcherCallback = (filename) => {
// Adding or removing a file invalidates most of what we think we know about module resolution
this.moduleResolutionCache.clear();
originalCallback(this.getScriptPathForTS(filename));
};

return this.ts.sys.watchDirectory(path, callback, recursive, options);
};
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/language-server/glint-language-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export default class GlintLanguageServer {
fileExists: this.transformManager.fileExists,
readFile: this.transformManager.readTransformedFile,
readDirectory: this.transformManager.readDirectory,
// @ts-ignore: This hook was added in TS5, and is safely irrelevant in earlier versions. Once we drop support for 4.x, we can also remove this @ts-ignore comment.
resolveModuleNameLiterals: this.transformManager.resolveModuleNameLiterals,
getCompilationSettings: () => parsedConfig.options,
// Yes, this looks like a mismatch, but built-in lib declarations don't resolve
// correctly otherwise, and this is what the TS wiki uses in their code snippet.
Expand Down Expand Up @@ -119,6 +121,9 @@ export default class GlintLanguageServer {
if (filePath.startsWith(this.glintConfig.rootDir)) {
this.rootFileNames.add(this.transformManager.getScriptPathForTS(filePath));
}

// Adding or removing a file invalidates most of what we think we know about module resolution.
this.transformManager.moduleResolutionCache.clear();
}

public watchedFileDidChange(uri: string): void {
Expand All @@ -136,6 +141,9 @@ export default class GlintLanguageServer {
if (!companionPath || this.glintConfig.getSynthesizedScriptPathForTS(companionPath) !== path) {
this.rootFileNames.delete(this.glintConfig.getSynthesizedScriptPathForTS(path));
}

// Adding or removing a file invalidates most of what we think we know about module resolution.
this.transformManager.moduleResolutionCache.clear();
}

public getDiagnostics(uri: string): Array<Diagnostic> {
Expand Down
6 changes: 0 additions & 6 deletions test-packages/ts-ember-app/types/global.d.ts

This file was deleted.

6 changes: 0 additions & 6 deletions test-packages/ts-ember-preview-types/types/global.d.ts

This file was deleted.

0 comments on commit 2c66bab

Please sign in to comment.