-```
-
-### `auto-glint-nocheck`
-
-The `auto-glint-nocheck` script automatically adds a `{{! @glint-nocheck }}` comment at the top of any templates in the given files that currently have type errors.
-
-It accepts one or more globs specifying what files it should inspect for type errors.
-
-This script can be used when first adopting Glint in an existing project in order to immediately begin enforcing type safety for new templates while incrementally converting existing ones over time. Templates with a `@glint-nocheck` directive will still benefit from best-effort editor support for features such as hover information, go-to-definition, etc, though the quality of these features will improve the closer the template and its backing module are to being completely typesafe.
-
-Sample usage:
-
-```sh
-npx -p @glint/scripts auto-glint-nocheck '{app,tests}/**/*.{ts,hbs,gts}'
-```
-
-The `nocheck` directive prepended to multiline templates will include a brief explanatory comment. By default, this looks like `{{! @glint-nocheck: not typesafe yet }}`, but the message can be customized with the `--explanation` flag.
-
-**Note**: this script requires that `@glint/core` >= v0.9.6 be available locally in the project where you are running it.
diff --git a/packages/scripts/__tests__/auto-nocheck.test.ts b/packages/scripts/__tests__/auto-nocheck.test.ts
deleted file mode 100644
index 55ea8a089..000000000
--- a/packages/scripts/__tests__/auto-nocheck.test.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-import { stripIndent } from 'common-tags';
-import ora, { Ora } from 'ora';
-import { Project } from 'glint-monorepo-test-utils';
-
-import { afterEach, beforeEach, describe, test, expect } from 'vitest';
-import { autoNocheck } from '../src/lib/_auto-nocheck.js';
-
-describe.skip('auto-nocheck', () => {
- let spinner!: Ora;
- let project!: Project;
- beforeEach(async () => {
- spinner = ora({ isSilent: true });
- project = await Project.create();
- });
-
- afterEach(async () => {
- await project.destroy();
- });
-
- test('standalone template files', async () => {
- project.setGlintConfig({ environment: 'ember-loose' });
-
- let files = {
- 'app/components/good.ts': stripIndent`
- import Component from '@glimmer/component';
-
- export default class Good extends Component {
- target = 'World';
- }
- `,
- 'app/components/good.hbs': stripIndent`
-
- Hello, {{this.target}}!
-
- `,
- 'app/components/bad.hbs': stripIndent`
-
- Hello, {{this.target}}!
-
- `,
- };
-
- project.write(files);
-
- await autoNocheck(['app/**/*.{ts,hbs}'], { spinner, cwd: project.filePath('.') });
-
- expect(project.read('app/components/good.hbs')).toEqual(files['app/components/good.hbs']);
- expect(project.read('app/components/bad.hbs')).toEqual(
- `{{! @glint-nocheck: not typesafe yet }}\n${files['app/components/bad.hbs']}`,
- );
- });
-
- test('embedded template files', async () => {
- project.setGlintConfig({ environment: 'ember-loose' });
-
- let files = {
- 'tests/integration/good-test.ts': stripIndent`
- import { render } from '@ember/test-helpers';
- import { hbs } from 'ember-cli-htmlbars';
-
- async function goodTest() {
- await render<{ target: string }>(hbs\`
- Hello, {{this.target}}!
- \`);
- }
- `,
- 'tests/integration/bad-test.ts': stripIndent`
- import { render } from '@ember/test-helpers';
- import { hbs } from 'ember-cli-htmlbars';
-
- async function badTest() {
- await render<{}>(hbs\`
- Hello, {{this.target}}!
- \`);
- }
- `,
- 'tests/integration/one-liner-test.ts': stripIndent`
- import { render } from '@ember/test-helpers';
- import { hbs } from 'ember-cli-htmlbars';
-
- async function oneLinerTest() {
- await render<{}>(hbs\`Hello, {{this.target}}!\`);
- }
- `,
- };
-
- project.write(files);
-
- await autoNocheck(['tests/**/*.ts'], { spinner, cwd: project.filePath('.') });
-
- expect(project.read('tests/integration/good-test.ts')).toEqual(
- files['tests/integration/good-test.ts'],
- );
-
- let badTest = files['tests/integration/bad-test.ts'];
- let badTestInsertionIndex = badTest.indexOf('Hello,');
- expect(project.read('tests/integration/bad-test.ts')).toEqual(
- `${badTest.slice(
- 0,
- badTestInsertionIndex,
- )}{{! @glint-nocheck: not typesafe yet }}\n ${badTest.slice(badTestInsertionIndex)}`,
- );
-
- let oneLinerTest = files['tests/integration/one-liner-test.ts'];
- let oneLinerTestInsertionIndex = oneLinerTest.indexOf('Hello,');
- expect(project.read('tests/integration/one-liner-test.ts')).toEqual(
- `${oneLinerTest.slice(
- 0,
- oneLinerTestInsertionIndex,
- )}{{! @glint-nocheck }}${oneLinerTest.slice(oneLinerTestInsertionIndex)}`,
- );
- });
-
- test(' templates', async () => {
- project.setGlintConfig({ environment: ['ember-loose', 'ember-template-imports'] });
-
- let files = {
- 'app/components/good.gts': stripIndent`
- import Component from '@glimmer/component';
-
- export default class Good extends Component {
- target = 'World';
-
-
- Hello, {{this.target}}!
-
- }
- `,
- 'app/components/bad.gts': stripIndent`
- import Component from '@glimmer/component';
-
- export default class Bad extends Component {
-
- Hello, {{this.target}}!
-
- }
- `,
- 'app/components/one-liner.gts': stripIndent`
- import Component from '@glimmer/component';
-
- export default class OneLiner extends Component {
- Hello, {{this.target}}!
- }
- `,
- };
-
- project.write(files);
-
- await autoNocheck(['app/**/*.gts'], { spinner, cwd: project.filePath('.') });
-
- expect(project.read('app/components/good.gts')).toEqual(files['app/components/good.gts']);
-
- let bad = files['app/components/bad.gts'];
- let badInsertionIndex = bad.indexOf('Hello,');
- expect(project.read('app/components/bad.gts')).toEqual(
- `${bad.slice(0, badInsertionIndex)}{{! @glint-nocheck: not typesafe yet }}\n ${bad.slice(
- badInsertionIndex,
- )}`,
- );
-
- let oneLiner = files['app/components/one-liner.gts'];
- let oneLinerInsertionIndex = oneLiner.indexOf('Hello,');
- expect(project.read('app/components/one-liner.gts')).toEqual(
- `${oneLiner.slice(0, oneLinerInsertionIndex)}{{! @glint-nocheck }}${oneLiner.slice(
- oneLinerInsertionIndex,
- )}`,
- );
- });
-
- test('custom explanation', async () => {
- project.setGlintConfig({ environment: 'ember-loose' });
-
- let files = {
- 'app/components/bad.hbs': stripIndent`
-
- Hello, {{this.target}}!
-
- `,
- };
-
- project.write(files);
-
- await autoNocheck(['app/**/*.hbs', '--explanation', 'old and crufty'], {
- spinner,
- cwd: project.filePath('.'),
- });
-
- expect(project.read('app/components/bad.hbs')).toEqual(
- `{{! @glint-nocheck: old and crufty }}\n${files['app/components/bad.hbs']}`,
- );
- });
-});
diff --git a/packages/scripts/__tests__/tsconfig.json b/packages/scripts/__tests__/tsconfig.json
deleted file mode 100644
index 47d10673e..000000000
--- a/packages/scripts/__tests__/tsconfig.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "extends": "../tsconfig.json",
- "include": ["*.ts", "../src/**/*.ts"]
-}
diff --git a/packages/scripts/package.json b/packages/scripts/package.json
deleted file mode 100644
index 86be19784..000000000
--- a/packages/scripts/package.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "name": "@glint/scripts",
- "version": "1.4.0",
- "type": "module",
- "repository": "typed-ember/glint",
- "description": "Scripts for working with Glint",
- "license": "MIT",
- "authors": [
- "Dan Freeman (https://github.com/dfreeman)",
- "Chris Krycho (https://github.com/chriskrycho)"
- ],
- "files": [
- "README.md",
- "lib",
- "bin"
- ],
- "bin": {
- "auto-glint-nocheck": "bin/auto-nocheck.js",
- "migrate-glintrc": "bin/migrate-glintrc.js"
- },
- "scripts": {
- "test": "vitest run",
- "test:typecheck": "echo 'no standalone typecheck within this project'",
- "test:tsc": "echo 'no standalone typecheck within this project'",
- "build": "tsc --build",
- "prepack": "yarn build"
- },
- "dependencies": {
- "glob": "^7.2.0",
- "golden-fleece": "^1.0.9",
- "js-yaml": "^4.1.0",
- "ora": "^6.1.2",
- "true-myth": "^6.1.0",
- "yargs": "^17.5.1",
- "zod": "^3.22.3"
- },
- "devDependencies": {
- "@types/js-yaml": "^4.0.5",
- "@types/yargs": "^17.0.10",
- "json5": "^2.2.2",
- "vitest": "~1.0.0"
- },
- "publishConfig": {
- "access": "public"
- }
-}
diff --git a/packages/scripts/src/auto-nocheck.ts b/packages/scripts/src/auto-nocheck.ts
deleted file mode 100644
index 49ea4b638..000000000
--- a/packages/scripts/src/auto-nocheck.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env node
-
-// import { autoNocheck } from './lib/_auto-nocheck.js';
-
-try {
- throw new Error('Not yet implemented since Volar');
- // await autoNocheck(process.argv.slice(2));
- process.exit(0);
-} catch (error: any) {
- console.error(error?.message ?? error);
- process.exit(1);
-}
diff --git a/packages/scripts/src/lib/_auto-nocheck.ts b/packages/scripts/src/lib/_auto-nocheck.ts
deleted file mode 100644
index 36c69620f..000000000
--- a/packages/scripts/src/lib/_auto-nocheck.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-import { relative, dirname } from 'node:path';
-import { readFile, writeFile } from 'node:fs/promises';
-import { createRequire } from 'node:module';
-import { fileURLToPath, pathToFileURL } from 'node:url';
-import globPkg from 'glob';
-import yargs from 'yargs';
-import ora from 'ora';
-// import { type ProjectAnalysis } from '@glint/core';
-
-// const globSync = globPkg.sync;
-
-// export async function autoNocheck(
-// args: Array,
-// { cwd = process.cwd(), spinner = ora() } = {},
-// ): Promise {
-// let glint = await loadGlintCore(cwd);
-// let { globs, explanation } = parseArgs(args);
-
-// spinner.start('Starting Glint language service...');
-
-// let project = glint.analyzeProject(cwd);
-// let fileUpdates = new Map();
-
-// for (let filePath of collectFilePaths(globs, cwd)) {
-// spinner.text = `Checking ${relative(cwd, filePath)}...`;
-
-// let fileContents = await readFile(filePath, 'utf-8');
-// let templatesWithErrors = findTemplatesWithErrors(glint, filePath, fileContents, project);
-// if (templatesWithErrors.size) {
-// fileUpdates.set(
-// filePath,
-// insertNocheckComments(filePath, fileContents, explanation, templatesWithErrors),
-// );
-// }
-// }
-
-// project.shutdown();
-
-// spinner.text = 'Writing `@glint-nocheck` comments...';
-
-// await writeFileContents(fileUpdates);
-
-// spinner.succeed(`Done! ${fileUpdates.size} files updated.`);
-// }
-
-// function parseArgs(args: Array): { globs: Array; explanation: string } {
-// return yargs(args)
-// .scriptName('auto-nocheck')
-// .command('$0 ', 'Write a description here', (command) =>
-// command
-// .positional('globs', {
-// type: 'string',
-// array: true,
-// describe: 'One or more paths or globs specifying the files to act on.',
-// demandOption: true,
-// })
-// .option('explanation', {
-// type: 'string',
-// default: 'not typesafe yet',
-// describe: 'The explanation to be included in @glint-nocheck comments',
-// }),
-// )
-// .wrap(100)
-// .strict()
-// .parseSync();
-// }
-
-// type GlintCore = typeof import('@glint/core');
-
-// function findImport(path: string, basedir: string): string {
-// let resolvedPath = createRequire(`${basedir}/package.json`).resolve(path);
-// let directory = dirname(fileURLToPath(import.meta.url));
-// return relative(directory, resolvedPath);
-// }
-
-// // We want to use the project-local version of @glint/core for maximum compatibility,
-// // but we need to guard against older versions that don't expose a programmatic API.
-// async function loadGlintCore(cwd: string): Promise {
-// let glint: GlintCore | undefined;
-// try {
-// glint = await import(findImport('@glint/core', cwd));
-// } catch (error) {
-// console.log(error);
-// // Fall through
-// }
-
-// if (!glint?.pathUtils) {
-// throw new Error(
-// 'This script requires a recent version of @glint/core to run. ' +
-// 'Consider upgrading to the latest version of Glint or using ' +
-// 'an older version of @glint/scripts.',
-// );
-// }
-
-// return glint;
-// }
-
-// function collectFilePaths(globs: Array, cwd: string): Array {
-// return globs.flatMap((glob) => globSync(glob, { cwd, absolute: true }));
-// }
-
-// // For the given file path, returns all templates in that file that have
-// // type errors, mapped from their offset to their contents. (This shape
-// // is a little weird, but it conveniently deduplicates and gives us exactly
-// // the information we need.)
-// function findTemplatesWithErrors(
-// glint: GlintCore,
-// filePath: string,
-// fileContents: string,
-// project: ProjectAnalysis,
-// ): Map {
-// let templatesWithErrors = new Map();
-// let info = project.transformManager.findTransformInfoForOriginalFile(filePath);
-// if (!info?.transformedModule) return templatesWithErrors;
-
-// // disabled for now to skip analyzeProject
-
-// // let { DiagnosticCategory } = project.glintConfig.ts;
-// // let diagnostics = project.languageServer.getDiagnostics(pathToFileURL(filePath).toString());
-// // let errors = diagnostics.filter((diagnostic) => diagnostic.severity === DiagnosticCategory.Error);
-
-// // for (let error of errors) {
-// // let originalStart = glint.pathUtils.positionToOffset(fileContents, error.range.start);
-// // let template = info.transformedModule.findTemplateAtOriginalOffset(filePath, originalStart);
-// // if (template) {
-// // templatesWithErrors.set(template.originalContentStart, template.originalContent);
-// // }
-// // }
-
-// return templatesWithErrors;
-// }
-
-// // Given information about a file and the location of templates within it
-// // that have type errors, returns a version of that file's contents with
-// // appropriate `{{! @glint-nocheck }}` comments added.
-// function insertNocheckComments(
-// filePath: string,
-// fileContents: string,
-// explanation: string,
-// templatesWithErrors: Map,
-// ): string {
-// let chunks = [];
-// let progress = 0;
-// for (let [offset, template] of templatesWithErrors.entries()) {
-// let isMultiline = /\n/.test(template) || filePath.endsWith('.hbs');
-// let [leadingWhitespace, indentation] = /^\s*?\n(\s*)/.exec(template) ?? ['', ''];
-// let insertAt = offset + leadingWhitespace.length;
-// let toInsert = isMultiline
-// ? `{{! @glint-nocheck: ${explanation} }}\n${indentation}`
-// : '{{! @glint-nocheck }}';
-
-// chunks.push(fileContents.slice(progress, insertAt));
-// chunks.push(toInsert);
-
-// progress = insertAt;
-// }
-
-// chunks.push(fileContents.slice(progress));
-
-// return chunks.join('');
-// }
-
-// async function writeFileContents(fileUpdates: Map): Promise {
-// for (let [path, contents] of fileUpdates.entries()) {
-// await writeFile(path, contents);
-// }
-// }
diff --git a/packages/scripts/tsconfig.json b/packages/scripts/tsconfig.json
deleted file mode 100644
index ed2573bb7..000000000
--- a/packages/scripts/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "extends": "../../tsconfig.compileroptions.json",
- "compilerOptions": {
- "rootDir": "src",
- "outDir": "bin"
- },
- "include": ["src"],
- "references": [{ "path": "../core" }]
-}
diff --git a/test-packages/test-utils/src/composite-project.ts b/test-packages/test-utils/src/composite-project.ts
deleted file mode 100644
index b0c69000d..000000000
--- a/test-packages/test-utils/src/composite-project.ts
+++ /dev/null
@@ -1,172 +0,0 @@
-import { symlinkSync } from 'fs';
-import * as path from 'path';
-
-import { Project } from './project.js';
-
-export const INPUT_DIR = 'src';
-export const INPUT_SFC = path.join(INPUT_DIR, 'index.gts');
-export const INPUT_SCRIPT = path.join(INPUT_DIR, 'index.ts');
-export const INPUT_TEMPLATE = path.join(INPUT_DIR, 'index.hbs');
-
-export const OUT_DIR = 'dist';
-
-// Changed for Volar from `index.d.ts`. Changing to `index.gts.d.ts` brings
-// this more closely in line with Vue and others' solutions for `.d.ts` files
-// on custom extensions, but we need to see how wide the breaking changes are.
-export const INDEX_D_TS = path.join(OUT_DIR, 'index.gts.d.ts');
-
-export const BASE_TS_CONFIG = {
- compilerOptions: {
- strict: true,
- target: 'es2019',
- module: 'es2015',
- moduleResolution: 'node',
- skipLibCheck: true,
- allowJs: true,
- checkJs: false,
- declaration: true,
-
- // commented out for Volar; Volar-ized `tsc` variants (e.g. `vue-tsc` as well as `glint`)
- // behave more closely to `tsc`, and it is expected for `noEmit` to be explicitly
- // specified in CLI args.
- // noEmit: false,
-
- incremental: true,
- noEmitOnError: true,
- outDir: OUT_DIR,
- },
- include: [INPUT_DIR],
- glint: { environment: 'ember-template-imports' },
-};
-
-export type CompositeProject = {
- root: Project;
- main: Project;
- children: {
- a: Project;
- b: Project;
- c: Project;
- };
-};
-
-export async function setupCompositeProject(): Promise {
- // Here, we create a `root` project which extends from a shared tsconfig,
- // which we create immediately below, as well as a local-types directory
- // which sets up the environment.
- const SHARED_TSCONFIG = 'shared.tsconfig.json';
- let root = await Project.createExact(
- {
- extends: `./${SHARED_TSCONFIG}`,
- references: [{ path: './main' }],
- include: ['./local-types/**/*'],
- files: [],
- },
- {
- private: true,
- workspaces: ['./main', './a', './b', './c'],
- },
- );
-
- root.write(SHARED_TSCONFIG, JSON.stringify(BASE_TS_CONFIG, null, 2));
- root.mkdir('local-types');
- root.write('local-types/index.d.ts', 'import "@glint/environment-ember-template-imports";');
-
- /**
- * Project Structure:
- *
- * ---> a
- * /
- * - main
- * \
- * ---> b ---> c
- *
- * Flattened:
- *
- * - main
- * - reference/dependency a
- * - reference/dependency b
- * - a
- * - reference/dependency c
- * - b
- * - no references
- * - c
- * - no references
- */
- let main = await Project.createExact(
- {
- extends: `../${SHARED_TSCONFIG}`,
- compilerOptions: { composite: true, outDir: OUT_DIR, rootDir: INPUT_DIR },
- include: ['../local-types/**/*', `${INPUT_DIR}/**/*`],
- references: [{ path: '../a' }, { path: '../b' }],
- glint: { environment: 'ember-template-imports' },
- },
- {
- name: '@glint-test/main',
- version: '1.0.0',
- types: `./${INDEX_D_TS}`,
- dependencies: {
- '@glint-test/a': 'link:../a',
- '@glint-test/b': 'link:../b',
- },
- },
- root.filePath('main'),
- );
-
- let a = await Project.createExact(
- {
- extends: `../${SHARED_TSCONFIG}`,
- compilerOptions: { composite: true, outDir: OUT_DIR, rootDir: INPUT_DIR },
- include: ['../local-types/**/*', `${INPUT_DIR}/**/*`],
- references: [{ path: '../c' }],
- glint: { environment: 'ember-template-imports' },
- },
- {
- name: '@glint-test/a',
- version: '1.0.0',
- types: `./${INDEX_D_TS}`,
- dependencies: {
- '@glint-test/c': 'link:../c',
- },
- },
- root.filePath('a'),
- );
-
- let b = await Project.createExact(
- {
- extends: `../${SHARED_TSCONFIG}`,
- compilerOptions: { composite: true, outDir: OUT_DIR, rootDir: INPUT_DIR },
- include: ['../local-types/**/*', `${INPUT_DIR}/**/*`],
- glint: { environment: 'ember-template-imports' },
- },
- {
- name: '@glint-test/b',
- version: '1.0.0',
- types: `./${INDEX_D_TS}`,
- },
- root.filePath('b'),
- );
-
- let c = await Project.createExact(
- {
- extends: `../${SHARED_TSCONFIG}`,
- compilerOptions: { composite: true, outDir: OUT_DIR, rootDir: INPUT_DIR },
- include: ['../local-types/**/*', `${INPUT_DIR}/**/*`],
- glint: { environment: 'ember-template-imports' },
- },
- {
- name: '@glint-test/c',
- version: '1.0.0',
- types: `./${INDEX_D_TS}`,
- },
- root.filePath('c'),
- );
-
- main.mkdir('node_modules/@glint-test');
- symlinkSync(a.filePath('.'), main.filePath('node_modules/@glint-test/a'));
- symlinkSync(b.filePath('.'), main.filePath('node_modules/@glint-test/b'));
-
- a.mkdir('node_modules/@glint-test');
- symlinkSync(c.filePath('.'), a.filePath('node_modules/@glint-test/c'));
-
- return { root, main, children: { a, b, c } };
-}
diff --git a/test-packages/test-utils/src/index.ts b/test-packages/test-utils/src/index.ts
index 3d7422711..195ed92db 100644
--- a/test-packages/test-utils/src/index.ts
+++ b/test-packages/test-utils/src/index.ts
@@ -1,3 +1 @@
-export * from './project.js';
-export * from './composite-project.js';
export * from './shared-test-workspace.js';
diff --git a/test-packages/test-utils/src/project.ts b/test-packages/test-utils/src/project.ts
deleted file mode 100644
index 5d3272df0..000000000
--- a/test-packages/test-utils/src/project.ts
+++ /dev/null
@@ -1,392 +0,0 @@
-import * as path from 'node:path';
-import * as fs from 'node:fs';
-import { fileURLToPath } from 'node:url';
-import { createRequire } from 'node:module';
-import { execaNode, ExecaChildProcess, Options } from 'execa';
-import { type GlintConfigInput } from '@glint/core/config-types';
-import { pathUtils } from '@glint/core';
-import { startLanguageServer, LanguageServerHandle } from '@volar/test-utils';
-import { FullDocumentDiagnosticReport, TextDocument } from '@volar/language-service';
-import { URI } from 'vscode-uri';
-import { Diagnostic } from 'typescript';
-import { Position, Range, TextEdit } from '@volar/language-server';
-import { WorkspaceSymbolRequest, WorkspaceSymbolParams } from '@volar/language-server/node.js';
-
-const require = createRequire(import.meta.url);
-const dirname = path.dirname(fileURLToPath(import.meta.url));
-const fileUriToTemplatePackage = pathUtils.filePathToUri(
- pathUtils.normalizeFilePath(path.resolve(dirname, '../../../packages/template')),
-);
-const fileUriToEmberTemplateImportsPackage = pathUtils.filePathToUri(
- pathUtils.normalizeFilePath(
- path.resolve(dirname, '../../../packages/environment-ember-template-imports'),
- ),
-);
-const ROOT = pathUtils.normalizeFilePath(path.resolve(dirname, '../../ephemeral'));
-
-// You'd think this would exist, but... no? Accordingly, supply a minimal
-// definition for our purposes here in tests.
-interface TsconfigWithGlint {
- extends?: string;
- compilerOptions?: Record; // no appropriate types exist :sigh:
- references?: Array<{ path: string }>;
- files?: Array;
- include?: Array;
- exclude?: Array;
- glint?: GlintConfigInput;
-}
-
-const newWorkingDir = (): string =>
- pathUtils.normalizeFilePath(path.join(ROOT, Math.random().toString(16).slice(2)));
-
-// export type LanguageServerHandle = ReturnType;
-
-export class Project {
- private rootDir: string;
- private languageServerHandle?: LanguageServerHandle;
- openedDocuments: TextDocument[] = [];
-
- private constructor(rootDir: string) {
- this.rootDir = rootDir;
- }
-
- public filePath(fileName: string): string {
- return pathUtils.normalizeFilePath(path.join(this.rootDir, fileName));
- }
-
- public fileURI(fileName: string): string {
- return pathUtils.filePathToUri(this.filePath(fileName));
- }
-
- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
- public async startLanguageServer() {
- if (this.languageServerHandle) {
- throw new Error('Language server is already running');
- }
-
- const languageServerHandle = startLanguageServer('../core/bin/glint-language-server.js');
- this.languageServerHandle = languageServerHandle;
-
- const initializeParams = {};
-
- // We need to construct a capabilities object that mirrors how VScode + similar editors
- // will initialize the Language Server.
- const capabilities = {
- workspace: {
- // Needed for tests that use didChangeWatchedFiles
- didChangeWatchedFiles: {
- // Unsure if these are needed at some point, but if so we'll need to implement addition hooks/commands to support.
- // dynamicRegistration: true,
- // relativePatternSupport: true,
- },
- symbols: {},
-
- // Enable pull-based diagnostics.
- diagnostics: {},
- },
- };
-
- await this.languageServerHandle
- .initialize(this.rootDir, initializeParams, capabilities)
- .catch((e) => {
- console.error(e);
- throw e;
- });
-
- const wrapForSnapshottability = (serviceMethodName: keyof typeof languageServerHandle) => {
- return async (uri: string, ...rest: any[]) => {
- // @ts-expect-error not sure how to type this
- const value = await languageServerHandle[serviceMethodName](uri, ...rest);
- return this.normalizeForSnapshotting(uri, value);
- };
- };
-
- return {
- ...this.languageServerHandle,
-
- sendDocumentDiagnosticRequest: wrapForSnapshottability('sendDocumentDiagnosticRequest') as (
- uri: string,
- ) => Promise,
- sendDefinitionRequest: wrapForSnapshottability('sendDefinitionRequest'),
- sendHoverRequest: wrapForSnapshottability('sendHoverRequest'),
-
- // Volar test-utils doesn't provide this, would be nice to upstream this.
- sendWorkspaceSymbolRequest: async (query: string) => {
- return languageServerHandle.connection.sendRequest(WorkspaceSymbolRequest.type, {
- query,
- } satisfies WorkspaceSymbolParams);
- },
-
- /**
- * Helper fn that makes it easier to replace the whole contents of a file,
- * rather than having to manually construct the Range and TextEdit.
- */
- replaceTextDocument: async (uri: string, text: string) => {
- // await languageServerHandle.replaceTextDocument(uri, text);
-
- // Create a Range that represents the whole document
- const wholeDocumentRange = Range.create(
- Position.create(0, 0),
- Position.create(Number.MAX_VALUE, Number.MAX_VALUE),
- );
-
- const textEdit = TextEdit.replace(wholeDocumentRange, text);
-
- return await languageServerHandle.updateTextDocument(uri, [textEdit]);
- },
- };
- }
-
- /**
- * Processes the language server return object passed in and converts any absolute URIs to
- * local files (which differ between localhost and CI) to static strings
- * so that they can be easily snapshotted in tests using `toMatchInlineSnapshot`.
- *
- * @param uri
- * @param diagnosticItems
- * @returns array of diagnostic
- */
- normalizeForSnapshotting(uri: string, object: unknown): unknown {
- let stringified = JSON.stringify(object);
-
- const volarEmbeddedContentUri = URI.from({
- scheme: 'volar-embedded-content',
- authority: 'ts',
- path: '/' + encodeURIComponent(uri),
- });
-
- const normalized = stringified
- .replaceAll(
- volarEmbeddedContentUri.toString(),
- `volar-embedded-content://URI_ENCODED_PATH_TO/FILE`,
- )
- .replaceAll(`"${this.filePath('.')}`, '"/path/to/EPHEMERAL_TEST_PROJECT')
- .replaceAll(`"${this.fileURI('.')}`, '"file:///path/to/EPHEMERAL_TEST_PROJECT')
- .replaceAll(fileUriToTemplatePackage, 'file:///PATH_TO_MODULE/@glint/template')
- .replaceAll(
- fileUriToEmberTemplateImportsPackage,
- 'file:///PATH_TO_MODULE/@glint/environment-ember-template-imports',
- );
-
- return JSON.parse(normalized);
- }
-
- /**
- * @param config A subset of `tsconfig.json` to use to configure the project
- * @param rootDir The directory in which to create the project. It is only
- * legal to pass in a directory which rooted in an *existing* project, so
- * if you need multiple projects, start by creating a project *without* this
- * and then create a directory using {@link filePath}.
- */
- public static async create(
- config: TsconfigWithGlint = {},
- rootDir = newWorkingDir(),
- ): Promise {
- if (!rootDir.includes(ROOT)) {
- throw new Error('Cannot create projects outside of `ROOT` dir');
- }
-
- let project = new Project(rootDir);
- let tsconfig = {
- compilerOptions: {
- strict: true,
- target: 'es2019',
- module: 'es2015',
- moduleResolution: 'node',
- skipLibCheck: true,
- allowJs: true,
- checkJs: false,
- ...config.compilerOptions,
- },
- glint: config.glint ?? {
- environment: ['ember-loose', 'ember-template-imports'],
- },
- };
-
- fs.rmSync(project.rootDir, { recursive: true, force: true });
- fs.mkdirSync(project.rootDir, { recursive: true });
-
- fs.writeFileSync(path.join(project.rootDir, 'package.json'), '{}');
- fs.writeFileSync(
- path.join(project.rootDir, 'tsconfig.json'),
- JSON.stringify(tsconfig, null, 2),
- );
-
- return project;
- }
-
- /**
- * An alternative to `create` which does not supply an default values for the
- * config, and therefore has no possibility of odd merges, instead allowing
- * (but also requiring) the caller to supply it in full.
- */
- public static async createExact(
- tsconfig: TsconfigWithGlint,
- packageJson: Record = {},
- rootDir = newWorkingDir(),
- ): Promise {
- if (!rootDir.includes(ROOT)) {
- throw new Error('Cannot create projects outside of `ROOT` dir');
- }
-
- let project = new Project(rootDir);
-
- fs.rmSync(project.rootDir, { recursive: true, force: true });
- fs.mkdirSync(project.rootDir, { recursive: true });
-
- project.write('package.json', JSON.stringify(packageJson, null, 2));
- project.write('tsconfig.json', JSON.stringify(tsconfig, null, 2));
-
- return project;
- }
-
- public updateTsconfig(update: (config: TsconfigWithGlint) => TsconfigWithGlint | void): void {
- let tsconfig = JSON.parse(this.read('tsconfig.json'));
- this.write('tsconfig.json', JSON.stringify(update(tsconfig) ?? tsconfig, null, 2));
- }
-
- public setGlintConfig(config: TsconfigWithGlint['glint']): void {
- this.updateTsconfig((tsconfig) => {
- tsconfig.glint = config;
- });
- }
-
- public write(files: Record): void;
- public write(fileName: string, fileContent: string): void;
- public write(...args: [Record] | [string, string]): void {
- let files: Record;
- if (args.length === 2) {
- files = { [args[0]]: args[1] };
- } else {
- files = args[0];
- }
-
- for (let [fileName, fileContent] of Object.entries(files)) {
- fs.mkdirSync(path.dirname(this.filePath(fileName)), { recursive: true });
- fs.writeFileSync(this.filePath(fileName), fileContent);
- }
- }
-
- public readdir(dirName = '.'): Array {
- return fs.readdirSync(this.filePath(dirName));
- }
-
- public read(fileName: string): string {
- return fs.readFileSync(this.filePath(fileName), 'utf-8');
- }
-
- public remove(fileName: string): void {
- fs.unlinkSync(this.filePath(fileName));
- }
-
- public rename(fromName: string, toName: string): void {
- fs.renameSync(this.filePath(fromName), this.filePath(toName));
- }
-
- public mkdir(name: string): void {
- fs.mkdirSync(this.filePath(name), { recursive: true });
- }
-
- public async destroy(): Promise {
- this.languageServerHandle?.connection.dispose();
-
- fs.rmSync(this.rootDir, { recursive: true, force: true });
- }
-
- public check(options: Options & { flags?: string[] } = {}): ExecaChildProcess {
- if (!options.flags) {
- options.flags = [];
- }
-
- // Not sure why this is needed, but in some contexts, `--pretty` is disabled
- // because TS doesn't detect a TTY setup.
- // https://github.com/microsoft/TypeScript/blob/c38569655bb151ec351c27032fbd3ef43b8856ba/src/compiler/executeCommandLine.ts#L160
- options.flags = [...options.flags, '--noEmit', '--pretty'];
-
- return execaNode(require.resolve('@glint/core/bin/glint'), options.flags, {
- cwd: this.rootDir,
- ...options,
- });
- }
-
- public checkWatch(options: Options & { flags?: string[] } = {}): Watch {
- let flags = ['--watch', ...(options.flags ?? [])];
- return new Watch(this.check({ ...options, flags, reject: false }));
- }
-
- public buildDeclaration(
- options: Options & { flags?: string[] } = {},
- debug = false,
- ): ExecaChildProcess {
- let flags = ['--build', '--emitDeclarationOnly', '--pretty', ...(options.flags ?? [])];
- return execaNode(require.resolve('@glint/core/bin/glint'), flags, {
- cwd: this.rootDir,
- nodeOptions: debug ? ['--inspect-brk'] : [],
- ...options,
- });
- }
-
- public buildClean(): ExecaChildProcess {
- return execaNode(require.resolve('@glint/core/bin/glint'), ['--build', '--clean'], {
- cwd: this.rootDir,
- });
- }
-
- public buildDeclarationWatch(options: Options & { flags?: string[] } = {}): Watch {
- let flags = ['--watch', ...(options.flags ?? [])];
- return new Watch(this.buildDeclaration({ ...options, flags, reject: false }));
- }
-}
-
-class Watch {
- allOutput = '';
-
- public constructor(private process: ExecaChildProcess) {
- this.process.stdout?.on('data', this.collectAllOutput);
- this.process.stderr?.on('data', this.collectAllOutput);
- }
-
- public awaitOutput(target: string, { timeout = 20_000 } = {}): Promise {
- return new Promise((resolve, reject) => {
- let output = '';
- let handleOutput = (chunk: any): void => {
- output += chunk.toString();
- if (output.includes(target)) {
- detach();
- resolve(output);
- }
- };
-
- let timeoutHandle = setTimeout(() => {
- detach();
- reject(
- new Error(
- `Timed out waiting to see ${JSON.stringify(target)}. Instead saw: ${JSON.stringify(
- output,
- )}`,
- ),
- );
- }, timeout);
-
- let detach = (): void => {
- clearTimeout(timeoutHandle);
- this.process.stdout?.off('data', handleOutput);
- this.process.stderr?.off('data', handleOutput);
- };
-
- this.process.stdout?.on('data', handleOutput);
- this.process.stderr?.on('data', handleOutput);
- });
- }
-
- private collectAllOutput = (chunk: any): void => {
- this.allOutput += chunk.toString();
- };
-
- public terminate(): ExecaChildProcess {
- this.process.stdout?.off('data', this.collectAllOutput);
- this.process.stderr?.off('data', this.collectAllOutput);
- this.process.kill();
- return this.process;
- }
-}
diff --git a/tsconfig.json b/tsconfig.json
index c3cfa7d2e..4b4b2172e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,7 +7,6 @@
{ "path": "./packages/type-test" },
{ "path": "./packages/tsserver-plugin" },
{ "path": "./packages/vscode" },
- { "path": "./packages/scripts" },
{ "path": "./test-packages/test-utils" }
],
"files": []