Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ninety-apples-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lit-labs/tsserver-plugin': patch
---

Optimize tests a bit.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,23 @@ import path from 'node:path';
import {describe as suite, test} from 'node:test';
import * as ts from 'typescript';
import {getLitExpressionType} from '../../lib/type-helpers/lit-expression-type.js';
import {getReusableTestProjectService} from '../project-service.js';

// Ensure the declarations are part of the program (side-effect import only).
import './fake-lit-html-types.js';

// Helper to build a Program including our fake lit-html types file.
function buildProgram(): ts.Program {
// Build the path relative to the package root (cwd during tests) to avoid
// accidentally duplicating path segments. The previous value incorrectly
// prefixed the repository path twice when running inside the package.
const fakeTypesPath = path.resolve('src/test/lib/fake-lit-html-types.ts');
const options: ts.CompilerOptions = {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.NodeNext,
moduleResolution: ts.ModuleResolutionKind.NodeNext,
strict: true,
esModuleInterop: true,
skipLibCheck: true,
};
return ts.createProgram([fakeTypesPath], options);
function getProgram(): ts.Program {
using cleanup = getReusableTestProjectService();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

const projectService = cleanup.projectService;
const expressionTypesPath = path.resolve(
'test-files/basic-templates/src/expression-types.ts'
);
const result = projectService.openClientFile(expressionTypesPath);
if (!result.configFileName) {
throw new Error('Did not associate file with a config');
}
const info = projectService.getScriptInfo(expressionTypesPath)!;
const project = info.containingProjects[0];
const program = project.getLanguageService().getProgram();
if (!program) throw new Error('No program');
return program;
}

function getExportType(
Expand All @@ -30,7 +28,7 @@ function getExportType(
): ts.Type | undefined {
const sourceFiles = program.getSourceFiles();
const fakeFile = sourceFiles.find((f) =>
/fake-lit-html-types\.ts$/.test(f.fileName)
/expression-types\.ts$/.test(f.fileName)
);
if (!fakeFile) return undefined;
const checker = program.getTypeChecker();
Expand All @@ -54,7 +52,7 @@ suite('getLitExpressionType', () => {
}

test('sentinel unique symbols map to any', () => {
const program = buildProgram();
const program = getProgram();
const checker = program.getTypeChecker();
// Use the direct exported sentinel symbols so the type has the correct symbol name.
const noChangeType = getExportType(program, 'noChange');
Expand All @@ -70,7 +68,7 @@ suite('getLitExpressionType', () => {
});

test('DirectiveResult unwraps to render return type (primitive)', () => {
const program = buildProgram();
const program = getProgram();
const checker = program.getTypeChecker();
const directiveResultNumber = getExportType(
program,
Expand All @@ -86,7 +84,7 @@ suite('getLitExpressionType', () => {
});

test('DirectiveResult unwraps to union of render return types', () => {
const program = buildProgram();
const program = getProgram();
const checker = program.getTypeChecker();
const directiveResultUnion = getExportType(program, 'directiveResultUnion');
assert.strictEqual(
Expand All @@ -110,7 +108,7 @@ suite('getLitExpressionType', () => {
});

test('Union containing DirectiveResult unwraps only that member', () => {
const program = buildProgram();
const program = getProgram();
const checker = program.getTypeChecker();
const mixed = getExportType(program, 'directiveResultOrString');
assert.strictEqual(
Expand All @@ -134,7 +132,7 @@ suite('getLitExpressionType', () => {
});

test('Union with sentinel symbol filters out the sentinel', () => {
const program = buildProgram();
const program = getProgram();
const checker = program.getTypeChecker();
const sentinelUnion = getExportType(program, 'sentinelUnion');
const transformed = getLitExpressionType(sentinelUnion!, ts, program);
Expand All @@ -146,21 +144,21 @@ suite('getLitExpressionType', () => {
});

test('Plain non-special type is unchanged', () => {
const plainProgram = buildProgram();
const checker = plainProgram.getTypeChecker();
const plainNumber = getExportType(plainProgram, 'plainNumber');
const program = getProgram();
const checker = program.getTypeChecker();
const plainNumber = getExportType(program, 'plainNumber');
assert.strictEqual(checker.typeToString(plainNumber!), '123');
const result = getLitExpressionType(plainNumber!, ts, plainProgram);
const result = getLitExpressionType(plainNumber!, ts, program);
assert.strictEqual(result, plainNumber);
// literal 123 keeps its literal type.
assert.strictEqual(checker.typeToString(result), '123');
});

test('Union without special values is unchanged', () => {
const plainProgram = buildProgram();
const checker = plainProgram.getTypeChecker();
const plainUnion = getExportType(plainProgram, 'plainUnion');
const result = getLitExpressionType(plainUnion!, ts, plainProgram);
const program = getProgram();
const checker = program.getTypeChecker();
const plainUnion = getExportType(program, 'plainUnion');
const result = getLitExpressionType(plainUnion!, ts, program);
assert.strictEqual(result, plainUnion);
assert.strictEqual(
normalizeUnion(checker.typeToString(plainUnion!)),
Expand All @@ -171,39 +169,27 @@ suite('getLitExpressionType', () => {
test('Sentinel-like symbol outside lit-html scope not treated as special', () => {
// Create a synthetic unique symbol type named noChange but from a different file.
const sourceText = 'export const noChange = Symbol("local");';
const tempFile = path.resolve('src/test/lib/local-file.ts');
const compilerOptions: ts.CompilerOptions = {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.NodeNext,
moduleResolution: ts.ModuleResolutionKind.NodeNext,
strict: true,
};
const host = ts.createCompilerHost(compilerOptions);
host.readFile = (fileName) => {
if (fileName === tempFile) return sourceText;
return ts.sys.readFile(fileName);
};
host.fileExists = (fileName) => {
if (fileName === tempFile) return true;
return ts.sys.fileExists(fileName);
};
const shadowProgram = ts.createProgram(
[path.resolve('src/test/lib/fake-lit-html-types.ts'), tempFile],
compilerOptions,
host
const tempFile = path.resolve(
'test-files/basic-templates/src/local-sentinel.ts'
);
const checker = shadowProgram.getTypeChecker();
const sf = shadowProgram
using cleanup = getReusableTestProjectService();
const projectService = cleanup.projectService;
// Simulate the file existing in memory only: open with content.
projectService.openClientFile(tempFile, sourceText);
const info = projectService.getScriptInfo(tempFile)!;
const project = info.containingProjects[0];
const program = project.getLanguageService().getProgram()!;
const checker = program.getTypeChecker();
const sf = program
.getSourceFiles()
.find((f) => /local-file\.ts$/.test(f.fileName))!;
.find((f) => /local-sentinel\.ts$/.test(f.fileName))!;
const moduleSymbol = checker.getSymbolAtLocation(sf)!;
const localSymbol = checker
.getExportsOfModule(moduleSymbol)
.find((s) => s.getEscapedName() === 'noChange')!;
const localType = checker.getTypeOfSymbol(localSymbol);
assert.ok(localType, 'got localtype');
const result = getLitExpressionType(localType, ts, shadowProgram);
// Should be unchanged (unique symbol stays unique symbol) not widened to any.
const result = getLitExpressionType(localType, ts, program);
assert.strictEqual(result, localType);
assert.strictEqual(checker.typeToString(localType), 'unique symbol');
assert.strictEqual(checker.typeToString(result), 'unique symbol');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert';
import * as path from 'node:path';
import {describe as suite, test} from 'node:test';
import {createTestProjectService} from '../project-service.js';
import {getReusableTestProjectService} from '../project-service.js';
import {getLitTemplateExpressions} from '@lit-labs/analyzer/lib/lit/template.js';
import ts from 'typescript';

Expand All @@ -10,7 +10,8 @@ function setupLanguageService(pathName: string): {
program: ts.Program;
testSourceFile: ts.SourceFile;
} {
const projectService = createTestProjectService();
using cleanup = getReusableTestProjectService();
const projectService = cleanup.projectService;
const result = projectService.openClientFile(pathName);
assert.ok(result.configFileName);

Expand Down
46 changes: 33 additions & 13 deletions packages/labs/tsserver-plugin/src/test/project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import ts from 'typescript';

/* eslint-disable @typescript-eslint/no-explicit-any */

const fakeFileWatcher: ts.FileWatcher = {
close() {},
};
const fakeFileWatcher: ts.FileWatcher = {close() {}};

const serverHost: ts.server.ServerHost = {
...ts.sys,
Expand All @@ -29,23 +27,29 @@ const serverHost: ts.server.ServerHost = {
},

setTimeout(
_callback: (...args: any[]) => void,
_ms: number,
..._args: any[]
callback: (...args: any[]) => void,
ms: number,
...args: any[]
): any {
throw new Error('Method not implemented.');
return globalThis.setTimeout(callback, ms, ...args);
},

clearTimeout(_timeoutId: any): void {
throw new Error('Method not implemented.');
clearTimeout(timeoutId: any): void {
globalThis.clearTimeout(timeoutId);
},

setImmediate(_callback: (...args: any[]) => void, ..._args: any[]): any {
throw new Error('Method not implemented.');
setImmediate(callback: (...args: any[]) => void, ...args: any[]): any {
return (globalThis as any).setImmediate
? (globalThis as any).setImmediate(callback, ...args)
: globalThis.setTimeout(callback, 0, ...args);
},

clearImmediate(_timeoutId: any): void {
throw new Error('Method not implemented.');
clearImmediate(timeoutId: any): void {
if ((globalThis as any).clearImmediate) {
(globalThis as any).clearImmediate(timeoutId);
} else {
globalThis.clearTimeout(timeoutId);
}
},
};

Expand Down Expand Up @@ -91,3 +95,19 @@ export const createTestProjectService = () => {
});
return projectService;
};

let reusableProjectService: ts.server.ProjectService | undefined;
export const getReusableTestProjectService = () => {
if (!reusableProjectService) {
reusableProjectService = createTestProjectService();
}
const projectService = reusableProjectService;
return {
projectService,
[Symbol.dispose]() {
for (const [path] of projectService.openFiles) {
projectService.closeClientFile(path);
}
},
};
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import assert from 'node:assert';
import * as path from 'node:path';
import {describe as suite, test} from 'node:test';
import {createTestProjectService} from '../project-service.js';
import {LitDiagnosticCode} from '../../lib/diagnostic-codes.js';
import {getReusableTestProjectService} from '../project-service.js';

suite('no-binding-like-attribute-names', () => {
test('Reports on property-binding-like attribute names', () => {
const projectService = createTestProjectService();
using cleanup = getReusableTestProjectService();
const projectService = cleanup.projectService;

const pathName = path.resolve(
'test-files/basic-templates/src/bad-attribute-name.ts'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import assert from 'node:assert';
import * as path from 'node:path';
import {describe as suite, test} from 'node:test';
import {createTestProjectService} from '../project-service.js';
import {getReusableTestProjectService} from '../project-service.js';
import {LitDiagnosticCode} from '../../lib/diagnostic-codes.js';
import type {Diagnostic} from 'typescript';

Expand All @@ -21,7 +21,8 @@ function assertDiagnosticMessages(

suite('no-unassignable-property-bindings', () => {
test('Unknown property diagnostic', () => {
const projectService = createTestProjectService();
using cleanup = getReusableTestProjectService();
const projectService = cleanup.projectService;
const file = path.resolve(
'test-files/basic-templates/src/property-binding-unknown.ts'
);
Expand All @@ -40,7 +41,8 @@ suite('no-unassignable-property-bindings', () => {
});

test('No diagnostics for assignable bindings', () => {
const projectService = createTestProjectService();
using cleanup = getReusableTestProjectService();
const projectService = cleanup.projectService;
const file = path.resolve(
'test-files/basic-templates/src/property-binding-assignable.ts'
);
Expand All @@ -60,7 +62,8 @@ suite('no-unassignable-property-bindings', () => {
});

test('Unassignable bindings produce diagnostics', () => {
const projectService = createTestProjectService();
using cleanup = getReusableTestProjectService();
const projectService = cleanup.projectService;
const file = path.resolve(
'test-files/basic-templates/src/property-binding-unassignable.ts'
);
Expand Down
Loading
Loading