Temporary commit to show an external implementation of declaration emit.

This commit is contained in:
Titian Cernicova-Dragomir
2023-03-16 13:47:03 +00:00
parent 17e7807089
commit ed568e20f1
74 changed files with 16957 additions and 2 deletions

5
external-declarations/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
/build
/node_modules
/src/*.tsbuildinfo
/tests/actual
/tsc-tests

View File

@@ -0,0 +1,25 @@
{
"name": "external-declarations",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "node ../built/local/tsc.js -p ./src",
"watch": "node ../built/local/tsc.js -w -p ./src",
"run-tests-parallel": "node ./build/test-runner/parallel-run.js --rootPaths=../tests/cases --libPath=../tests/lib --type=all --shardCount=8",
"run-test": "node ./build/test-runner/test-runner-main.js --type=all --rootPaths=c:/dev/TSC/TypeScript/tests/cases ",
"transform-tests-parallel": "node ./build/code-mod/parallel-run.js --rootPaths=../tests/cases --shardCount=8",
"transform-test": "node ./build/code-mod/test-updater.js --rootPaths=../tests/cases",
"run-transformed-tests-parallel": "node ./build/test-runner/parallel-run.js --rootPaths=./tsc-tests/updated-tests --libPath=../tests/lib --type=all --shardCount=8",
"run-transformed-test": "node ./build/test-runner/test-runner-main.js --type=all --rootPaths=c:/dev/TSC/TypeScript/tests/cases "
},
"author": "",
"license": "ISC",
"dependencies": {
"source-map-support": "^0.5.21",
"typescript": "../"
},
"devDependencies": {
"@types/node": "^18.11.18"
}
}

View File

@@ -0,0 +1,18 @@
# Implementation of declaration emit
This implementation uses the the parser and printer from the TypeScript code base but replaces the binder and emit resolver with rewritten versions that do not depend on the type checker.
The declaration transform itself is mostly the same as the version in TypeScript (with some code erased and different imports)
## Package scripts
- `build`/ `watch` - Build the code
- `run-tests-parallel` - Emits declarations using tsc and the stand alone emitter for the tests in the TypeScript code base in parallel. Outputs to `tsc-tests`
- `run-test` - Emits declarations using tsc and the stand alone emitter for the tests in the TypeScript code base on a single thread, or filtered if you specify a test name. Outputs to `tsc-tests`
- `transform-tests-parallel` - Transforms the TypeScript tests t add missing type annotations and write them to `tsc-tests\updated-tests`. Runs in parallel
- `transform-test` - Transforms the TypeScript tests t add missing type annotations and write them to `tsc-tests\updated-tests`. Runs on a single thread. Accepts a test name or regex filter
- `run-transformed-tests-parallel` - Same as `run-tests-parallel` but runs on the transformed tests in `tsc-tests\updated-tests`
- `run-transformed-test`- Same as `run-test` but runs on the transformed tests in `tsc-tests\updated-tests`
Note: Tests currently just output the declarations, there is no console error message. Use an external diff tool to see differences.

View File

@@ -0,0 +1,276 @@
import * as ts from "typescript";
import { NodeBuilderFlags } from "typescript";
import { map } from "../compiler/lang-utils";
import { SymbolTracker } from "../compiler/types";
const declarationEmitNodeBuilderFlags =
NodeBuilderFlags.MultilineObjectLiterals |
NodeBuilderFlags.WriteClassExpressionAsTypeLiteral |
NodeBuilderFlags.UseTypeOfFunction |
NodeBuilderFlags.UseStructuralFallback |
NodeBuilderFlags.AllowEmptyTuple |
NodeBuilderFlags.GenerateNamesForShadowedTypeParams |
NodeBuilderFlags.NoTruncation;
// Define a transformer function
export function addTypeAnnotationTransformer(program: ts.Program, moduleResolutionHost?: ts.ModuleResolutionHost) {
function tryGetReturnType(
typeChecker: ts.TypeChecker,
node: ts.SignatureDeclaration
): ts.Type | undefined {
const signature = typeChecker.getSignatureFromDeclaration(node);
if (signature) {
return typeChecker.getReturnTypeOfSignature(signature);
}
}
function isVarConst(node: ts.VariableDeclaration | ts.VariableDeclarationList): boolean {
return !!(ts.getCombinedNodeFlags(node) & ts.NodeFlags.Const);
}
function isDeclarationReadonly(declaration: ts.Declaration): boolean {
return !!(ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Readonly && !ts.isParameterPropertyDeclaration(declaration, declaration.parent));
}
function isLiteralConstDeclaration(node: ts.VariableDeclaration | ts.PropertyDeclaration | ts.PropertySignature | ts.ParameterDeclaration): boolean {
if (isDeclarationReadonly(node) || ts.isVariableDeclaration(node) && isVarConst(node)) {
// TODO: Make sure this is a valid approximation for literal types
return !node.type && 'initializer' in node && !!node.initializer && ts.isLiteralExpression(node.initializer);
// Original TS version
// return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node)));
}
return false;
}
const typeChecker = program.getTypeChecker();
return (context: ts.TransformationContext) => {
let hasError = false;
let reportError = () => {
hasError = true;
}
const symbolTracker: SymbolTracker | undefined = !moduleResolutionHost? undefined : {
trackSymbol(){ return false; },
reportInaccessibleThisError: reportError,
reportInaccessibleUniqueSymbolError: reportError,
reportCyclicStructureError: reportError,
reportPrivateInBaseOfClassExpression: reportError,
reportLikelyUnsafeImportRequiredError: reportError,
reportTruncationError: reportError,
moduleResolverHost: moduleResolutionHost as any,
trackReferencedAmbientModule(){},
trackExternalModuleSymbolOfImportTypeNode(){},
reportNonlocalAugmentation(){},
reportNonSerializableProperty(){},
reportImportTypeNodeResolutionModeOverride() {},
};
function typeToTypeNode(type: ts.Type, enclosingDeclaration: ts.Node) {
const typeNode = typeChecker.typeToTypeNode(
type,
enclosingDeclaration,
declarationEmitNodeBuilderFlags,
// @ts-expect-error Use undocumented parameters
symbolTracker,
)
if (hasError) {
hasError = false;
return undefined;
}
return typeNode;
}
// Return a visitor function
return (rootNode: ts.Node) => {
function updateTypesInNodeArray<T extends ts.Node>(nodeArray: ts.NodeArray<T>): ts.NodeArray<T>
function updateTypesInNodeArray<T extends ts.Node>(nodeArray: ts.NodeArray<T> | undefined): ts.NodeArray<T> | undefined
function updateTypesInNodeArray<T extends ts.Node>(nodeArray: ts.NodeArray<T> | undefined) {
if(nodeArray === undefined) return undefined;
return ts.factory.createNodeArray(
nodeArray.map(param => {
return visit(param) as ts.ParameterDeclaration;
})
)
}
// Define a visitor function
function visit(node: ts.Node): ts.Node | ts.Node[] {
if(ts.isParameter(node) && !node.type) {
const type = typeChecker.getTypeAtLocation(node);
if(type) {
const typeNode = typeToTypeNode(type, node);
return ts.factory.updateParameterDeclaration(
node,
node.modifiers,
node.dotDotDotToken,
node.name,
node.questionToken,
typeNode,
node.initializer
)
}
}
// Check if node is a variable declaration
if (ts.isVariableDeclaration(node) && !node.type && !isLiteralConstDeclaration(node)) {
const type = typeChecker.getTypeAtLocation(node);
const typeNode = typeToTypeNode(type, node)
return ts.factory.updateVariableDeclaration(
node,
node.name,
undefined,
typeNode,
node.initializer
);
}
if (ts.isFunctionDeclaration(node) && !node.type) {
const type = tryGetReturnType(typeChecker, node);
if(type) {
const typeNode = typeToTypeNode(type, node);
return ts.factory.updateFunctionDeclaration(
node,
node.modifiers,
node.asteriskToken,
node.name,
updateTypesInNodeArray(node.typeParameters),
updateTypesInNodeArray(node.parameters),
typeNode,
node.body
)
}
}
if(ts.isPropertySignature(node) && !node.type && !isLiteralConstDeclaration(node)) {
const type = typeChecker.getTypeAtLocation(node);
const typeNode = typeToTypeNode(type, node);
return ts.factory.updatePropertySignature(
node,
node.modifiers,
node.name,
node.questionToken,
typeNode,
);
}
if(ts.isPropertyDeclaration(node) && !node.type && !isLiteralConstDeclaration(node)) {
const type = typeChecker.getTypeAtLocation(node);
const typeNode = typeToTypeNode(type, node);
return ts.factory.updatePropertyDeclaration(
node,
node.modifiers,
node.name,
node.questionToken ?? node.exclamationToken,
typeNode,
node.initializer
);
}
if(ts.isMethodSignature(node) && !node.type) {
const type = tryGetReturnType(typeChecker, node);
if(type) {
const typeNode = typeToTypeNode(type, node);
return ts.factory.updateMethodSignature(
node,
node.modifiers,
node.name,
node.questionToken,
updateTypesInNodeArray(node.typeParameters),
updateTypesInNodeArray(node.parameters),
typeNode,
);
}
}
if(ts.isCallSignatureDeclaration(node)) {
const type = tryGetReturnType(typeChecker, node);
if(type) {
const typeNode = typeToTypeNode(type, node);
return ts.factory.updateCallSignature(
node,
updateTypesInNodeArray(node.typeParameters),
updateTypesInNodeArray(node.parameters),
typeNode,
)
}
}
if(ts.isMethodDeclaration(node) && !node.type) {
const type = tryGetReturnType(typeChecker, node);
if(type) {
const typeNode = typeToTypeNode(type, node);
return ts.factory.updateMethodDeclaration(
node,
node.modifiers,
node.asteriskToken,
node.name,
node.questionToken,
updateTypesInNodeArray(node.typeParameters),
updateTypesInNodeArray(node.parameters),
typeNode,
node.body,
);
}
}
if(ts.isGetAccessorDeclaration(node) && !node.type) {
const type = tryGetReturnType(typeChecker, node);
if(type) {
const typeNode = typeToTypeNode(type, node);
return ts.factory.updateGetAccessorDeclaration(
node,
node.modifiers,
node.name,
updateTypesInNodeArray(node.parameters),
typeNode,
node.body,
);
}
}
if(ts.isSetAccessorDeclaration(node) && !node.parameters[0]?.type) {
return ts.factory.updateSetAccessorDeclaration(
node,
node.modifiers,
node.name,
updateTypesInNodeArray(node.parameters),
node.body,
);
}
if ( ts.isConstructorDeclaration(node)) {
return ts.factory.updateConstructorDeclaration(
node,
node.modifiers,
updateTypesInNodeArray(node.parameters),
node.body,
)
}
if(ts.isConstructSignatureDeclaration(node)) {
const type = tryGetReturnType(typeChecker, node);
if(type) {
const typeNode = typeToTypeNode(type, node);
return ts.factory.updateConstructSignature(
node,
updateTypesInNodeArray(node.typeParameters),
updateTypesInNodeArray(node.parameters),
typeNode,
)
}
}
if(ts.isExportAssignment(node) && node.expression.kind !== ts.SyntaxKind.Identifier) {
const type = typeChecker.getTypeAtLocation(node.expression);
if(type) {
const typeNode = typeToTypeNode(type, node);
const newId = ts.factory.createIdentifier("_default");
const varDecl = ts.factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, typeNode, /*initializer*/ undefined);
const statement = ts.factory.createVariableStatement(
[],
ts.factory.createVariableDeclarationList([varDecl], ts.NodeFlags.Const)
);
return [statement, ts.factory.updateExportAssignment(node, node.modifiers, newId)];
}
}
// Otherwise, visit each child node recursively
return ts.visitEachChild(node, visit, context);
}
// Start visiting from root node
return ts.visitNode(rootNode, visit)!;
};
};
}

View File

@@ -0,0 +1,35 @@
// Import TypeScript module
import * as ts from "typescript";
import { isDeclarationFileName } from "../compiler/utils";
import { addTypeAnnotationTransformer } from "./code-transform";
(ts as any).Debug .enableDebugInfo();
// Read tsconfig.json file from disk
const tsconfig = ts.readConfigFile("tsconfig.json", ts.sys.readFile);
// Parse JSON content to get compiler options and file names
const parsed = ts.parseJsonConfigFileContent(tsconfig.config, ts.sys, "./");
const options = parsed.options;
// Pass compiler options and file names to createProgram
const program = ts.createProgram(parsed.fileNames, options);
program.getSemanticDiagnostics();
const files = program.getSourceFiles();
for (const file of files) {
if (isDeclarationFileName(file.fileName)) continue;
const transformedFile = ts.transform(file, [
addTypeAnnotationTransformer(program),
]);
const printer = ts.createPrinter({
onlyPrintJsDocStyle: true,
newLine: options.newLine,
target: options.target,
} as ts.PrinterOptions);
const resultStr = printer.printFile(
transformedFile.transformed[0] as ts.SourceFile
);
console.log(resultStr);
}

View File

@@ -0,0 +1,88 @@
import * as fs from 'fs'
import * as path from 'path'
import * as childProcess from "child_process";
import { ArgType, parseArgs } from '../utils/cli-parser';
type ExecuteResult = {
error: childProcess.ExecException | null
stdout: string,
stderr: string,
}
function exec(cmd: string, dir: string, onStdOut: (s: string) => void) {
return new Promise<ExecuteResult>((resolve) => {
console.log(`In ${dir} Executing: ${cmd}`);
const ls = childProcess.spawn(cmd, [], {
cwd: path.resolve(path.join(process.cwd(), dir)),
shell: true
});
let stdout = ""
let stderr = ""
ls.stdout.on('data', function (data) {
if(!onStdOut) {
process.stdout.write(data.toString());
} else {
onStdOut(data.toString());
}
stdout += data.toString();
});
ls.stderr.on('data', function (data) {
process.stderr.write(data.toString());
stderr += data.toString();
});
ls.on('error', function(err) {
console.log(err);
})
ls.on('exit', function (code) {
console.log('exited:' + code?.toString());
resolve({
error: !code ? null : Object.assign(new Error(""), {
code,
cmd: cmd,
}),
stderr,
stdout
})
});
})
}
const { value: parsedArgs, printUsageOnErrors } = parseArgs(process.argv.slice(2), {
default: {
type: ArgType.String(),
description: "Test filter to run",
},
rootPaths: ArgType.StringArray(),
shardCount: ArgType.Number(),
});
printUsageOnErrors();
const shardCount = parsedArgs.shardCount ?? 6;
async function main() {
let runCount = 0;
const commandLine = `node ./build/code-mod/test-updater.js ${process.argv.slice(2).join(" ")} ${
parsedArgs.shardCount === undefined ? `--shardCount=${shardCount} `: ""
}`;
let lastWrite = new Date().getTime();
const startTime = new Date().getTime();
const elapsedTime = (now: number) => `${((now - startTime) / 1000 / 60).toFixed(2)} minutes`
const promisees = Array.from({ length: shardCount}).map(async (_, index) => {
await exec(commandLine + ` --shard=${index}`, "./", out => {
runCount += (out.match(/Ran:/g) || []).length;
if(new Date().getTime() - lastWrite > 2000) {
lastWrite = new Date().getTime()
console.log(`Run count: ${runCount} after ${elapsedTime(lastWrite)}`);
}
});
console.log(`Shard ${index} completed`);
});
await Promise.all(promisees);
const endTime = new Date().getTime();
console.log(`Took ${elapsedTime(endTime)} to complete ${runCount}`)
}
main();

View File

@@ -0,0 +1,141 @@
import 'source-map-support/register';
import * as fsPath from 'path'
import * as fs from 'fs/promises'
import { parserConfiguration, ArgType, parseArgs } from "../utils/cli-parser";
import { isDeclarationFile, isJavaScriptFile, isJSONFile, isSourceMapFile, normalizePath } from '../compiler/path-utils';
import { isRelevantTestFile, loadTestCase } from '../test-runner/utils';
import ts = require('typescript');
import { compileFiles, setCompilerOptionsFromHarnessSetting, TestFile } from '../test-runner/tsc-infrastructure/compiler-run';
import { splitContentByNewlines, TestCaseContent, TestUnitData } from '../test-runner/tsc-infrastructure/test-file-parser';
import { addTypeAnnotationTransformer } from './code-transform';
import { ensureDir, readAllFiles } from '../utils/fs-utils';
(ts as any).Debug .enableDebugInfo();
export const testRunnerCLIConfiguration = parserConfiguration({
default: {
type: ArgType.String(),
description: "Test filter to run",
},
rootPaths: ArgType.StringArray(),
shard: ArgType.Number(),
shardCount: ArgType.Number(),
})
const excludeFilter =/\/fourslash\//;
const { value: parsedArgs, printUsageOnErrors } = parseArgs(process.argv.slice(2), testRunnerCLIConfiguration);
printUsageOnErrors();
const rootCasePaths = parsedArgs.rootPaths ?? [ './tests/sources' ]
const filter = parsedArgs.default ? new RegExp(parsedArgs.default) : /.*\.ts/
const shard = parsedArgs.shard
const shardCount = parsedArgs.shardCount
const allTests = rootCasePaths
.flatMap(r => readAllFiles(r, filter).map((file) => ({ file, root: r})))
.filter(f => !excludeFilter.exec(f.file));;
async function writeTestCase(testData: TestCaseContent & { BOM: string }, path: string) {
let lines = splitContentByNewlines(testData.code);
let result: string[] = [];
let copyFrom = 0;
function pushFrom(target: string[], source: string[], from: number = 0, to: number = source.length) {
for(let i = from; i< to; i++) {
target.push(source[i]);
}
}
for (const file of testData.testUnitData) {
if(file.content === undefined) continue;
pushFrom(result, lines, copyFrom, file.startLine)
pushFrom(result, splitContentByNewlines(file.content));
copyFrom = file.endLine + 1;
}
pushFrom(result, lines, copyFrom, lines.length)
await ensureDir(fsPath.dirname(path));
const content = testData.BOM + result.join(lines.delimiter);
await fs.writeFile(path, content);
}
async function main() {
const testsPerShared = shardCount && Math.round(allTests.length / shardCount);
const [start, end] = shard == undefined || shardCount == undefined || testsPerShared == undefined ?
[0, allTests.length] :
[shard * testsPerShared, (shard == shardCount - 1) ? allTests.length : (shard + 1) * testsPerShared];
for (let count = start; count < end; count++) {
const testFile = normalizePath(allTests[count].file);
const rootPath = normalizePath(allTests[count].root);
const caseData = await loadTestCase(testFile);
const settings: ts.CompilerOptions = {};
setCompilerOptionsFromHarnessSetting(caseData.settings, settings);
function createHarnessTestFile(lastUnit: TestUnitData): TestFile {
return { unitName: lastUnit.name, content: lastUnit.content, fileOptions: lastUnit.fileOptions };
}
const toBeCompiled = caseData.testUnitData.map(unit => {
return createHarnessTestFile(unit);
});
await writeTestCase(caseData, testFile.replace(rootPath, "./tsc-tests/original-tests"))
const result = compileFiles(toBeCompiled, [], {
declaration: "true",
isolatedDeclarations: "true",
removeComments: "false",
}, settings, undefined);
const program = result.program!;
for(const testFileContent of caseData.testUnitData) {
if(!isRelevantTestFile(testFileContent)) continue;
try {
const sourceFile = program.getSourceFile(testFileContent.name)!;
program.getDeclarationDiagnostics(sourceFile)
if(!sourceFile) continue;
let moduleResolutionHost: ts.ModuleResolutionHost| undefined = undefined;
program.emit(sourceFile, undefined, undefined, true, {
afterDeclarations:[
(c) => {
// @ts-expect-error getEmitHost is not exposed
moduleResolutionHost = c.getEmitHost();
return (v) => v;
}]
})
const transformedFile = ts.transform(sourceFile, [
addTypeAnnotationTransformer(program, moduleResolutionHost),
]);
const printer = ts.createPrinter({
onlyPrintJsDocStyle: true,
newLine: settings.newLine,
target: settings.target,
removeComments: false,
} as ts.PrinterOptions);
const resultStr = printer.printFile(
transformedFile.transformed[0] as ts.SourceFile
);
testFileContent.content = resultStr;
}catch(e) {
console.log(`Test ${testFile} failed to transform`)
process.exit();
}
}
await writeTestCase(caseData, testFile.replace(rootPath, "./tsc-tests/updated-tests"))
console.log(`Ran: ${count}`);
}
}
main();

View File

@@ -0,0 +1,319 @@
import { Symbol, Node, NodeArray, SyntaxKind, unescapeLeadingUnderscores, SortedReadonlyArray, NodeFlags, ModifierFlags, EmitFlags, SymbolFlags, TypeFlags, ObjectFlags, FlowFlags, FlowNodeBase, Type, symbolName, LiteralType, BigIntLiteralType, ObjectType, Signature, isIdentifier, idText, isPrivateIdentifier, isStringLiteral, isNumericLiteral, isBigIntLiteral, isTypeParameterDeclaration, isParameter, isConstructorDeclaration, isGetAccessorDeclaration, isSetAccessorDeclaration, isCallSignatureDeclaration, isConstructSignatureDeclaration, isIndexSignatureDeclaration, isTypePredicateNode, isTypeReferenceNode, isFunctionTypeNode, isConstructorTypeNode, isTypeQueryNode, isTypeLiteralNode, isArrayTypeNode, isTupleTypeNode, isOptionalTypeNode, isRestTypeNode, isUnionTypeNode, isIntersectionTypeNode, isConditionalTypeNode, isInferTypeNode, isParenthesizedTypeNode, isThisTypeNode, isTypeOperatorNode, isIndexedAccessTypeNode, isMappedTypeNode, isLiteralTypeNode, isNamedTupleMember, isImportTypeNode, isParseTreeNode, getParseTreeNode, FlowNode, FlowSwitchClause, FlowLabel, MapLike } from "typescript";
import * as ts from "typescript";
import { getSourceFileOfNode, getSourceTextOfNodeFromSourceFile} from "./utils";
import { every, map, stableSort, compareValues } from "./lang-utils";
/** @internal */
export const enum AssertionLevel {
None = 0,
Normal = 1,
Aggressive = 2,
VeryAggressive = 3,
}
type AssertionKeys = keyof typeof Debug;
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Indicates whether a map-like contains an own property with the specified key.
*
* @param map A map-like.
* @param key A property key.
*
* @internal
*/
export function hasProperty(map: MapLike<any>, key: string): boolean {
return hasOwnProperty.call(map, key);
}
/**
* Tests whether an assertion function should be executed. If it shouldn't, it is cached and replaced with `ts.noop`.
* Replaced assertion functions are restored when `Debug.setAssertionLevel` is set to a high enough level.
* @param level The minimum assertion level required.
* @param name The name of the current assertion function.
*/
function shouldAssertFunction<K extends AssertionKeys>(_level: AssertionLevel, _name: K): boolean {
return true;
}
type AnyFunction = (...a: any) => any;
/** @internal */
export namespace Debug {
export function fail(message?: string, stackCrawlMark?: AnyFunction): never {
debugger;
const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure.");
if ((Error as any).captureStackTrace) {
(Error as any).captureStackTrace(e, stackCrawlMark || fail);
}
throw e;
}
export function failBadSyntaxKind(node: Node, message?: string, stackCrawlMark?: AnyFunction): never {
return fail(
`${message || "Unexpected node."}\r\nNode ${formatSyntaxKind(node.kind)} was unexpected.`,
stackCrawlMark || failBadSyntaxKind);
}
export function assert(expression: unknown, message?: string, verboseDebugInfo?: string | (() => string), stackCrawlMark?: AnyFunction): asserts expression {
if (!expression) {
message = message ? `False expression: ${message}` : "False expression.";
if (verboseDebugInfo) {
message += "\r\nVerbose Debug Information: " + (typeof verboseDebugInfo === "string" ? verboseDebugInfo : verboseDebugInfo());
}
fail(message, stackCrawlMark || assert);
}
}
export function assertEqual<T>(a: T, b: T, msg?: string, msg2?: string, stackCrawlMark?: AnyFunction): void {
if (a !== b) {
const message = msg ? msg2 ? `${msg} ${msg2}` : msg : "";
fail(`Expected ${a} === ${b}. ${message}`, stackCrawlMark || assertEqual);
}
}
export function assertLessThan(a: number, b: number, msg?: string, stackCrawlMark?: AnyFunction): void {
if (a >= b) {
fail(`Expected ${a} < ${b}. ${msg || ""}`, stackCrawlMark || assertLessThan);
}
}
export function assertLessThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void {
if (a > b) {
fail(`Expected ${a} <= ${b}`, stackCrawlMark || assertLessThanOrEqual);
}
}
export function assertGreaterThanOrEqual(a: number, b: number, stackCrawlMark?: AnyFunction): void {
if (a < b) {
fail(`Expected ${a} >= ${b}`, stackCrawlMark || assertGreaterThanOrEqual);
}
}
export function assertIsDefined<T>(value: T, message?: string, stackCrawlMark?: AnyFunction): asserts value is NonNullable<T> {
// eslint-disable-next-line no-null/no-null
if (value === undefined || value === null) {
fail(message, stackCrawlMark || assertIsDefined);
}
}
export function checkDefined<T>(value: T | null | undefined, message?: string, stackCrawlMark?: AnyFunction): T {
assertIsDefined(value, message, stackCrawlMark || checkDefined);
return value;
}
export function assertEachIsDefined<T extends Node>(value: NodeArray<T>, message?: string, stackCrawlMark?: AnyFunction): asserts value is NodeArray<T>;
export function assertEachIsDefined<T>(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction): asserts value is readonly NonNullable<T>[];
export function assertEachIsDefined<T>(value: readonly T[], message?: string, stackCrawlMark?: AnyFunction) {
for (const v of value) {
assertIsDefined(v, message, stackCrawlMark || assertEachIsDefined);
}
}
export function checkEachDefined<T, A extends readonly T[]>(value: A, message?: string, stackCrawlMark?: AnyFunction): A {
assertEachIsDefined(value, message, stackCrawlMark || checkEachDefined);
return value;
}
export function assertNever(member: never, message = "Illegal value:", stackCrawlMark?: AnyFunction): never {
const detail = typeof member === "object" && hasProperty(member, "kind") && hasProperty(member, "pos") ? "SyntaxKind: " + formatSyntaxKind((member as Node).kind) : JSON.stringify(member);
return fail(`${message} ${detail}`, stackCrawlMark || assertNever);
}
export function assertEachNode<T extends Node, U extends T>(nodes: NodeArray<T>, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray<U>;
export function assertEachNode<T extends Node, U extends T>(nodes: readonly T[], test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[];
export function assertEachNode<T extends Node, U extends T>(nodes: NodeArray<T> | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is NodeArray<U> | undefined;
export function assertEachNode<T extends Node, U extends T>(nodes: readonly T[] | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts nodes is readonly U[] | undefined;
export function assertEachNode(nodes: readonly Node[], test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertEachNode(nodes: readonly Node[] | undefined, test: (node: Node) => boolean, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertEachNode")) {
assert(
test === undefined || every(nodes, test),
message || "Unexpected node.",
() => `Node array did not pass test '${getFunctionName(test)}'.`,
stackCrawlMark || assertEachNode);
}
}
export function assertNode<T extends Node, U extends T>(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U;
export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertNode")) {
assert(
node !== undefined && (test === undefined || test(node)),
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`,
stackCrawlMark || assertNode);
}
}
export function assertNotNode<T extends Node, U extends T>(node: T | undefined, test: (node: Node) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is Exclude<T, U>;
export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertNotNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertNotNode")) {
assert(
node === undefined || test === undefined || !test(node),
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node!.kind)} should not have passed test '${getFunctionName(test!)}'.`,
stackCrawlMark || assertNotNode);
}
}
export function assertOptionalNode<T extends Node, U extends T>(node: T, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U;
export function assertOptionalNode<T extends Node, U extends T>(node: T | undefined, test: (node: T) => node is U, message?: string, stackCrawlMark?: AnyFunction): asserts node is U | undefined;
export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertOptionalNode(node: Node | undefined, test: ((node: Node) => boolean) | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalNode")) {
assert(
test === undefined || node === undefined || test(node),
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node?.kind)} did not pass test '${getFunctionName(test!)}'.`,
stackCrawlMark || assertOptionalNode);
}
}
export function assertOptionalToken<T extends Node, K extends SyntaxKind>(node: T, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract<T, { readonly kind: K }>;
export function assertOptionalToken<T extends Node, K extends SyntaxKind>(node: T | undefined, kind: K, message?: string, stackCrawlMark?: AnyFunction): asserts node is Extract<T, { readonly kind: K }> | undefined;
export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction): void;
export function assertOptionalToken(node: Node | undefined, kind: SyntaxKind | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertOptionalToken")) {
assert(
kind === undefined || node === undefined || node.kind === kind,
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node?.kind)} was not a '${formatSyntaxKind(kind)}' token.`,
stackCrawlMark || assertOptionalToken);
}
}
export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction): asserts node is undefined;
export function assertMissingNode(node: Node | undefined, message?: string, stackCrawlMark?: AnyFunction) {
if (shouldAssertFunction(AssertionLevel.Normal, "assertMissingNode")) {
assert(
node === undefined,
message || "Unexpected node.",
() => `Node ${formatSyntaxKind(node!.kind)} was unexpected'.`,
stackCrawlMark || assertMissingNode);
}
}
/**
* Asserts a value has the specified type in typespace only (does not perform a runtime assertion).
* This is useful in cases where we switch on `node.kind` and can be reasonably sure the type is accurate, and
* as a result can reduce the number of unnecessary casts.
*/
export function type<T>(value: unknown): asserts value is T;
export function type(_value: unknown) { }
export function getFunctionName(func: AnyFunction) {
if (typeof func !== "function") {
return "";
}
else if (hasProperty(func, "name")) {
return (func as any).name;
}
else {
const text = Function.prototype.toString.call(func);
const match = /^function\s+([\w\$]+)\s*\(/.exec(text);
return match ? match[1] : "";
}
}
export function formatSymbol(symbol: Symbol): string {
return `{ name: ${unescapeLeadingUnderscores(symbol.escapedName)}; flags: ${formatSymbolFlags(symbol.flags)}; declarations: ${map(symbol.declarations, node => formatSyntaxKind(node.kind))} }`;
}
/**
* Formats an enum value as a string for debugging and debug assertions.
*/
export function formatEnum(value = 0, enumObject: any, isFlags?: boolean) {
const members = getEnumMembers(enumObject);
if (value === 0) {
return members.length > 0 && members[0][0] === 0 ? members[0][1] : "0";
}
if (isFlags) {
const result: string[] = [];
let remainingFlags = value;
for (const [enumValue, enumName] of members) {
if (enumValue > value) {
break;
}
if (enumValue !== 0 && enumValue & value) {
result.push(enumName);
remainingFlags &= ~enumValue;
}
}
if (remainingFlags === 0) {
return result.join("|");
}
}
else {
for (const [enumValue, enumName] of members) {
if (enumValue === value) {
return enumName;
}
}
}
return value.toString();
}
const enumMemberCache = new Map<any, SortedReadonlyArray<[number, string]>>();
function getEnumMembers(enumObject: any) {
// Assuming enum objects do not change at runtime, we can cache the enum members list
// to reuse later. This saves us from reconstructing this each and every time we call
// a formatting function (which can be expensive for large enums like SyntaxKind).
const existing = enumMemberCache.get(enumObject);
if (existing) {
return existing;
}
const result: [number, string][] = [];
for (const name in enumObject) {
const value = enumObject[name];
if (typeof value === "number") {
result.push([value, name]);
}
}
const sorted = stableSort<[number, string]>(result, (x, y) => compareValues(x[0], y[0]));
enumMemberCache.set(enumObject, sorted);
return sorted;
}
export function formatSyntaxKind(kind: SyntaxKind | undefined): string {
return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false);
}
export function formatNodeFlags(flags: NodeFlags | undefined): string {
return formatEnum(flags, (ts as any).NodeFlags, /*isFlags*/ true);
}
export function formatModifierFlags(flags: ModifierFlags | undefined): string {
return formatEnum(flags, (ts as any).ModifierFlags, /*isFlags*/ true);
}
export function formatEmitFlags(flags: EmitFlags | undefined): string {
return formatEnum(flags, (ts as any).EmitFlags, /*isFlags*/ true);
}
export function formatSymbolFlags(flags: SymbolFlags | undefined): string {
return formatEnum(flags, (ts as any).SymbolFlags, /*isFlags*/ true);
}
export function formatTypeFlags(flags: TypeFlags | undefined): string {
return formatEnum(flags, (ts as any).TypeFlags, /*isFlags*/ true);
}
export function formatObjectFlags(flags: ObjectFlags | undefined): string {
return formatEnum(flags, (ts as any).ObjectFlags, /*isFlags*/ true);
}
export function formatFlowFlags(flags: FlowFlags | undefined): string {
return formatEnum(flags, (ts as any).FlowFlags, /*isFlags*/ true);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,696 @@
import { Symbol, Node, VariableDeclaration, ParameterDeclaration, ModifierFlags, isLiteralExpression, ClassElement, isIdentifier, BindingPattern, SyntaxKind, findAncestor, SourceFile, isVariableStatement, SymbolFlags, isImportDeclaration, __String, isFunctionDeclaration, isClassDeclaration, isTypeAliasDeclaration, isExportDeclaration, isExportAssignment, isModuleDeclaration, NodeArray, isConstructSignatureDeclaration, isConstructorDeclaration, isImportEqualsDeclaration, isEnumDeclaration, isInterfaceDeclaration, isNamedExports, isModuleBlock, ModuleDeclaration, ArrayBindingElement, isExternalModuleReference, forEachChild, isMetaProperty, isComputedPropertyName, isPropertyAccessExpression, isPrivateIdentifier, Extension, isConditionalTypeNode, TypeElement, CompilerOptions, ModuleKind, ModuleDetectionKind, isMappedTypeNode, TypeParameterDeclaration, isInferTypeNode, isBlock, InterfaceDeclaration, ClassDeclaration, FunctionDeclaration, JsxEmit, isJsxFragment, isJsxOpeningLikeElement, ModuleResolutionKind, ResolutionMode, isEnumMember, getNameOfDeclaration, ExportDeclaration, Identifier, isSourceFile, ExportSpecifier, EnumDeclaration, isVariableDeclaration } from "typescript";
import { Debug } from "./debug";
import { forEach } from "./lang-utils";
import { _Symbol } from "./types";
import { isBindingPattern, getNodeId, hasSyntacticModifier, getEmitModuleKind, getEmitModuleResolutionKind, isEnumConst, nodeHasName } from "./utils";
export interface NodeLinks {
isVisible?: boolean;
symbol?: BasicSymbol;
localSymbol?: BasicSymbol;
locals?: SymbolTable;
}
type SymbolTable = Map<__String, BasicSymbol>;
export interface BasicSymbol {
name?: __String
exportSymbol?: BasicSymbol;
declarations: Node[];
signatureDeclarations?: Node[];
flags: SymbolFlags;
isVisible?: boolean;
members?: SymbolTable;
exports?: SymbolTable;
}
function assertNever(o: never): never {
throw new Error("Should never happen")
}
type _Node = Node;
declare module 'typescript' {
interface SourceFile {
externalModuleIndicator?: _Node | true;
}
}
export function getEmitModuleDetectionKind(options: CompilerOptions) {
return options.moduleDetection ||
(getEmitModuleKind(options) === ModuleKind.Node16 || getEmitModuleKind(options) === ModuleKind.NodeNext ? ModuleDetectionKind.Force : ModuleDetectionKind.Auto);
}
type SymbolRegistrationFlags = readonly [flags: SymbolFlags, forbiddenFlags: SymbolFlags];
const syntaxKindToSymbolMap = {
[SyntaxKind.TypeParameter]: [SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes],
[SyntaxKind.Parameter]: [SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes],
[SyntaxKind.VariableDeclaration]: [SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes],
[SyntaxKind.BindingElement]: [SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes],
[SyntaxKind.PropertyDeclaration]: [SymbolFlags.Property, SymbolFlags.PropertyExcludes],
[SyntaxKind.PropertySignature]: [SymbolFlags.Property, SymbolFlags.PropertyExcludes],
[SyntaxKind.PropertyAssignment]: [SymbolFlags.Property, SymbolFlags.PropertyExcludes],
[SyntaxKind.ShorthandPropertyAssignment]: [SymbolFlags.Property, SymbolFlags.PropertyExcludes],
[SyntaxKind.EnumMember]: [SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes],
[SyntaxKind.CallSignature]: [SymbolFlags.Signature, SymbolFlags.None],
[SyntaxKind.ConstructSignature]: [SymbolFlags.Signature, SymbolFlags.None],
[SyntaxKind.IndexSignature]: [SymbolFlags.Signature, SymbolFlags.None],
[SyntaxKind.MethodDeclaration]: [SymbolFlags.Method, SymbolFlags.MethodExcludes],
[SyntaxKind.MethodSignature]: [SymbolFlags.Method, SymbolFlags.MethodExcludes],
[SyntaxKind.FunctionDeclaration]: [SymbolFlags.Function, SymbolFlags.FunctionExcludes],
[SyntaxKind.Constructor]: [SymbolFlags.Constructor, SymbolFlags.None],
[SyntaxKind.GetAccessor]: [SymbolFlags.GetAccessor, SymbolFlags.GetAccessorExcludes],
[SyntaxKind.SetAccessor]: [SymbolFlags.SetAccessor, SymbolFlags.SetAccessorExcludes],
[SyntaxKind.ClassExpression]: [SymbolFlags.Class, SymbolFlags.ClassExcludes],
[SyntaxKind.ClassDeclaration]: [SymbolFlags.Class, SymbolFlags.ClassExcludes],
[SyntaxKind.InterfaceDeclaration]: [SymbolFlags.Interface, SymbolFlags.InterfaceExcludes],
[SyntaxKind.TypeAliasDeclaration]: [SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes],
[SyntaxKind.EnumDeclaration]: {
const: [SymbolFlags.ConstEnum, SymbolFlags.ConstEnumExcludes],
regular: [SymbolFlags.RegularEnum, SymbolFlags.RegularEnumExcludes],
},
[SyntaxKind.ModuleDeclaration]: {
value: [SymbolFlags.ValueModule, SymbolFlags.ValueModuleExcludes],
namespace: [SymbolFlags.NamespaceModule, SymbolFlags.NamespaceModuleExcludes],
},
[SyntaxKind.ImportEqualsDeclaration]: [SymbolFlags.Alias, SymbolFlags.AliasExcludes],
[SyntaxKind.NamespaceImport]: [SymbolFlags.Alias, SymbolFlags.AliasExcludes],
[SyntaxKind.ImportSpecifier]: [SymbolFlags.Alias, SymbolFlags.AliasExcludes],
[SyntaxKind.ExportSpecifier]: [SymbolFlags.Alias | SymbolFlags.ExportValue, SymbolFlags.AliasExcludes],
[SyntaxKind.NamespaceExportDeclaration]: [SymbolFlags.Alias, SymbolFlags.AliasExcludes],
[SyntaxKind.ImportClause]: [SymbolFlags.Alias, SymbolFlags.AliasExcludes],
} as const; //satisfies Partial<Record<SyntaxKind, SymbolRegistrationFlags | Record<string, SymbolRegistrationFlags>>>;
export function bindSourceFile(file: SourceFile, options: CompilerOptions, packageModuleType: ResolutionMode) {
const nodeLinks: NodeLinks[] = []
function tryGetNodeLinks(node: Node): NodeLinks | undefined {
const id = (node as any).id;
if(!id) return undefined;
return nodeLinks[id];
}
function getNodeLinks(node: Node): NodeLinks {
const nodeId = getNodeId(node);
return nodeLinks[nodeId] || (nodeLinks[nodeId] = {});
}
const [isFileAModule, isNodeAModuleIndicator] = getSetExternalModuleIndicator(options);
file.externalModuleIndicator =
isFileAModule(file) || isExternalModuleWorker(file, isNodeAModuleIndicator);
file.impliedNodeFormat = getImpliedNodeFormat(file.fileName, options, packageModuleType)
bind();
return {
tryGetNodeLinks,
getNodeLinks,
resolveName,
}
function resolveName(enclosingDeclaration: Node, escapedText: __String, meaning: SymbolFlags) {
function getSymbolFromScope(table: SymbolTable | undefined) {
let symbol = table?.get(escapedText);
if(symbol && ((symbol.flags & meaning) || (symbol.flags & SymbolFlags.Alias))) {
return symbol
}
}
let currentScope = enclosingDeclaration;
while(currentScope) {
const links = tryGetNodeLinks(currentScope);
let symbol = getSymbolFromScope(links?.locals);
if(!symbol && (isModuleDeclaration(currentScope) || isSourceFile(currentScope))) {
symbol = getSymbolFromScope(links?.symbol?.exports)
}
if(symbol) return symbol;
currentScope = currentScope.parent;
}
return undefined;
}
function getSymbolFlagsForNode(node: Node & { kind: keyof typeof syntaxKindToSymbolMap }) {
if(node.kind === SyntaxKind.EnumDeclaration) {
return isEnumConst(node as EnumDeclaration) ?
syntaxKindToSymbolMap[node.kind].const:
syntaxKindToSymbolMap[node.kind].regular;
}
if(node.kind === SyntaxKind.ModuleDeclaration) {
return getModuleInstanceState(node as ModuleDeclaration) === ModuleInstanceState.Instantiated ?
syntaxKindToSymbolMap[node.kind].value:
syntaxKindToSymbolMap[node.kind].namespace;
}
return syntaxKindToSymbolMap[node.kind];
}
function getElementFlagsOrThrow(node: Node) {
const result = getSymbolFlagsForNode(node as Node & { kind: keyof typeof syntaxKindToSymbolMap });
if(!result) {
throw new Error("Unknown binding element type");
}
return result;
}
function bind() {
let currentScope: Node = undefined!;
let currentSymbol: BasicSymbol = undefined!;
let currentLocalSymbolTable: SymbolTable = undefined!;
let currentExportsSymbolTable: SymbolTable | null = null;
let postBindingAction: Array<() => void> = [];
const fileLinks = getNodeLinks(file).symbol = newSymbol();
fileLinks.exports = new Map();
withScope(file, fileLinks.exports, ()=> bindEachFunctionsFirst(file.statements));
postBindingAction.forEach(fn => fn());
function newSymbol(): BasicSymbol {
return {
declarations: [],
flags: 0,
}
}
function getSymbol(table: SymbolTable, name: __String) {
let symbol = table.get(name);
if(!symbol) {
symbol = newSymbol();
symbol.name = name;
table.set(name, symbol)
}
return symbol;
}
function addLocalAndExportDeclaration(name: __String | undefined, node: Node, [flags, forbiddenFlags]: SymbolRegistrationFlags, isExport: boolean) {
if(isExport) {
const exportKind = flags & SymbolFlags.Value ? SymbolFlags.ExportValue : 0;
const localSymbol = addLocalOnlyDeclaration(name, node, [exportKind, forbiddenFlags]);
const exportSymbol = addExportOnlyDeclaration(name, node, [flags, forbiddenFlags]);
localSymbol.exportSymbol = exportSymbol
return exportSymbol;
}else {
return addLocalOnlyDeclaration(name, node, [flags, forbiddenFlags]);
}
}
function addExportOnlyDeclaration(name: __String | undefined, node: Node, flagsAndForbiddenFlags: SymbolRegistrationFlags) {
if(!currentExportsSymbolTable) {
throw new Error("Exporting symbol from a context that does not support it");
}
return addDeclaration(currentExportsSymbolTable, name, node, flagsAndForbiddenFlags);
}
function addLocalOnlyDeclaration(name: __String | undefined, node: Node, flagsAndForbiddenFlags: SymbolRegistrationFlags) {
return addDeclaration(currentLocalSymbolTable, name, node, flagsAndForbiddenFlags);
}
function addDeclaration(table: SymbolTable, name: __String | undefined, node: Node, [flags, forbiddenFlags]: SymbolRegistrationFlags) {
let symbol = name != null ? getSymbol(table, name) : newSymbol();
// Symbols don't merge, create new one
if(forbiddenFlags & symbol.flags) {
symbol = newSymbol();
}
symbol.declarations.push(node);
symbol.flags |= flags;
getNodeLinks(node).symbol = symbol;
node.symbol = symbol as unknown as Symbol;
return symbol;
}
function withScope(scope: Node, exports: SymbolTable | null, fn: () => void) {
const old = [currentScope, currentLocalSymbolTable, currentExportsSymbolTable] as const
currentScope = scope;
const links = getNodeLinks(scope);
currentLocalSymbolTable = (links.locals ??= new Map());
currentExportsSymbolTable = exports;
fn();
[currentScope, currentLocalSymbolTable, currentExportsSymbolTable] = old;
}
function withMembers(symbol: BasicSymbol, fn:()=> void, table: "members" | "exports" = "members") {
const old = [currentLocalSymbolTable, currentSymbol] as const
currentSymbol = symbol;
currentLocalSymbolTable = (symbol[table] ??= new Map());
fn();
[currentLocalSymbolTable, currentSymbol] = old;
}
/**
* Gets the symbolic name for a member from its type.
*/
function getMemberName(element: TypeElement | ClassElement): __String | undefined{
if (isConstructorDeclaration(element) || isConstructSignatureDeclaration(element)) {
return "@constructor" as __String;
}
const name = element.name;
if(!name) return undefined;
if(isIdentifier(name)) {
return name.escapedText;
}
else if(isLiteralExpression(name)) {
return `${name.text}` as __String;
}
else if(isComputedPropertyName(name)) {
let expr = name.expression;
if(isLiteralExpression(expr)) {
return `${expr.text}` as __String;
}
let fullName = ""
while(isPropertyAccessExpression(expr)) {
fullName = "." + expr.name.escapedText + name
expr = expr.expression;
}
if(!isIdentifier(expr)) {
return undefined;
}
return `[${expr.escapedText}${fullName}]` as __String;
} else if(isPrivateIdentifier(name)) {
return name.escapedText;
} else {
assertNever(name)
}
}
function getStatementName(s: InterfaceDeclaration | ClassDeclaration | FunctionDeclaration) {
if(hasSyntacticModifier(s, ModifierFlags.Export) && hasSyntacticModifier(s, ModifierFlags.Default)) {
return "@default" as __String;
}
if(s.name) {
return s.name.escapedText;
}
}
// We need to bind locals for types (conditional and mapped typed define parameters in their definitions)
function bindTypeExpressions(node?: Node) {
function bindChildren(node: Node) {
forEachChild(node, bindWorker, arr => arr.forEach(bindWorker));
}
function bindWorker(node?: Node) {
if(!node) return;
if(isMappedTypeNode(node)) {
const mappedType = node;
withScope(node, null, () => {
bindTypeParameters([mappedType.typeParameter]);
bindWorker(mappedType.nameType);
bindWorker(mappedType.type);
});
}
else if(isConditionalTypeNode(node)) {
withScope(node.checkType, null, () => {
bindWorker(node.extendsType);
})
getNodeLinks(node.trueType).locals = getNodeLinks(node.checkType).locals
} if(isInferTypeNode(node)) {
const conditionalTypeOwner = findAncestor(node, isConditionalTypeNode);
// Probably an error, infer not in a conditional type context
// Try to bind the rest of it
if(conditionalTypeOwner) {
withScope(conditionalTypeOwner, null, () => {
bindTypeParameters([node.typeParameter]);
});
}
bindChildren(node);
}
else if(isBlock(node)) {
// Do not go into bodies
return;
}
else {
bindChildren(node);
}
}
bindWorker(node)
}
function bindTypeParameters(typeParameters: TypeParameterDeclaration[] | NodeArray<TypeParameterDeclaration> | undefined) {
typeParameters?.forEach(t => addLocalOnlyDeclaration(t.name.escapedText, t, getSymbolFlagsForNode(t)));
}
function bindVariable(d: VariableDeclaration | ParameterDeclaration) {
bindTypeExpressions(d.type);
const isExported = isVariableDeclaration(d) && hasSyntacticModifier(d.parent.parent, ModifierFlags.Export);
if(isIdentifier(d.name)) {
addLocalAndExportDeclaration(d.name.escapedText, d, getSymbolFlagsForNode(d), isExported);
}
else if(isBindingPattern(d.name)) {
function bindBindingPattern(pattern: BindingPattern) {
// type BindingPattern = ObjectBindingPattern | ArrayBindingPattern;
(pattern.elements as NodeArray<ArrayBindingElement>).forEach(b => {
if(b.kind === SyntaxKind.OmittedExpression) return;
if(!b.name) return;
if(isIdentifier(b.name)) {
addLocalAndExportDeclaration(b.name.escapedText, b, getSymbolFlagsForNode(b), isExported);
} else {
bindBindingPattern(b.name);
}
})
}
bindBindingPattern(d.name);
} else {
assertNever(d.name)
}
}
function bindEachFunctionsFirst(nodes: NodeArray<Node> | undefined): void {
if(!nodes) return;
bindContainer(nodes.filter(n => n.kind === SyntaxKind.FunctionDeclaration));
bindContainer(nodes.filter(n => n.kind !== SyntaxKind.FunctionDeclaration));
}
function bindContainer(statements: NodeArray<Node> | Node[]) {
statements.forEach(statement => {
const isExported = hasSyntacticModifier(statement, ModifierFlags.Export);
if(isImportEqualsDeclaration(statement)) {
addLocalOnlyDeclaration(statement.name.escapedText, statement, getSymbolFlagsForNode(statement));
}
if(isImportDeclaration(statement)) {
if(!statement.importClause) {
return;
}
if(statement.importClause.name) {
addLocalOnlyDeclaration(statement.importClause.name.escapedText, statement.importClause, getSymbolFlagsForNode(statement.importClause));
}
if(statement.importClause.namedBindings) {
const namedBindings = statement.importClause.namedBindings;
if(namedBindings.kind === SyntaxKind.NamedImports) {
namedBindings.elements.forEach(v => {
addLocalOnlyDeclaration(v.name.escapedText, v, getSymbolFlagsForNode(v));
})
}
else if(namedBindings.kind === SyntaxKind.NamespaceImport) {
addLocalOnlyDeclaration(namedBindings.name.escapedText, namedBindings, getSymbolFlagsForNode(namedBindings));
} else {
debugger;
throw new Error("Not supported");
}
}
}
if(isVariableStatement(statement)) {
statement.declarationList.declarations.forEach(bindVariable);
}
if(isFunctionDeclaration(statement)) {
bindTypeParameters(statement.typeParameters);
bindTypeExpressions(statement.type);
withScope(statement, null, () => {
bindTypeExpressions(statement);
statement.parameters.forEach(bindVariable);
});
addLocalAndExportDeclaration(getStatementName(statement), statement, getSymbolFlagsForNode(statement), isExported);
}
if(isTypeAliasDeclaration(statement)) {
addLocalAndExportDeclaration(statement.name.escapedText, statement, getSymbolFlagsForNode(statement), isExported);
withScope(statement, null, () => {
bindTypeParameters(statement.typeParameters);
});
bindTypeExpressions(statement.type);
}
// Default export declarations set isVisible on true on associated symbols in the type checker.
if(isExportAssignment(statement)) {
if(statement.expression && isIdentifier(statement.expression)) {
const name = statement.expression.escapedText;
postBindingAction.push(() => {
const resolvedSymbol = resolveName(statement.expression, name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
if(resolvedSymbol) {
resolvedSymbol.isVisible = true;
resolvedSymbol.declarations.forEach(d => getNodeLinks(d).isVisible = true);
}
});
}
}
if(isExportDeclaration(statement)) {
if(statement.exportClause && isNamedExports(statement.exportClause)) {
const elements = statement.exportClause.elements;
if (statement.moduleSpecifier) {
// TODO is currentExportsSymbolTable ok here?
withScope(statement, null, () => {
elements.forEach(e => {
const [flags, forbiddenFlags] = getSymbolFlagsForNode(e)
addLocalOnlyDeclaration((e.propertyName ?? e.name).escapedText, e, [flags | SymbolFlags.ExportValue , forbiddenFlags]);
});
})
}
elements.forEach(e => {
postBindingAction.push(() => {
const resolvedSymbol = resolveName(e,(e.propertyName ?? e.name).escapedText, ~0);
if(resolvedSymbol) {
resolvedSymbol.isVisible = true;
resolvedSymbol.declarations.forEach(d => getNodeLinks(d).isVisible = true);
}
});
})
}
}
// if(isEnumMember(statement)) {
// addDeclaration(getNameOfDeclaration(statement.name), statement, getElementFlags(statement.kind));
// }
if(isEnumDeclaration(statement)) {
addLocalAndExportDeclaration(statement.name.escapedText, statement, getSymbolFlagsForNode(statement), isExported);
// withScope(statement, () => bindContainer(statement.members))
}
if(isModuleDeclaration(statement)) {
function bindModuleDeclaration(moduleDeclaration: ModuleDeclaration) {
const name = isIdentifier(moduleDeclaration.name) ? moduleDeclaration.name.escapedText: undefined;
const moduleSymbol = addLocalAndExportDeclaration(name, moduleDeclaration, getSymbolFlagsForNode(moduleDeclaration), isExported);
moduleSymbol.exports ??= new Map();
withScope(moduleDeclaration, moduleSymbol.exports, () => {
if(moduleDeclaration.body) {
if(isModuleBlock(moduleDeclaration.body)) {
const moduleBlock = moduleDeclaration.body
bindEachFunctionsFirst(moduleBlock.statements)
}
else if(isModuleDeclaration(moduleDeclaration.body)) {
const subModule = moduleDeclaration.body;
bindModuleDeclaration(subModule);
} else {
throw new Error("Unsupported body type");
}
}
});
}
bindModuleDeclaration(statement);
}
if(isInterfaceDeclaration(statement)) {
const interfaceDeclaration = statement;
const interfaceSymbol = addLocalAndExportDeclaration(interfaceDeclaration.name.escapedText, interfaceDeclaration, getSymbolFlagsForNode(interfaceDeclaration), isExported);
withScope(interfaceDeclaration, null, () =>{
bindTypeParameters(interfaceDeclaration.typeParameters);
});
withMembers(interfaceSymbol, () => {
interfaceDeclaration.members.forEach(m => {
addLocalOnlyDeclaration(getMemberName(m), m, getElementFlagsOrThrow(m));
bindTypeExpressions(m)
})
});
}
if(isClassDeclaration(statement)) {
const classDeclaration = statement;
const classSymbol = addLocalAndExportDeclaration(classDeclaration.name?.escapedText, classDeclaration, getSymbolFlagsForNode(classDeclaration), isExported);
withScope(classDeclaration, null, () =>{
bindTypeParameters(classDeclaration.typeParameters);
});
withMembers(classSymbol, () => {
classDeclaration.members.forEach(m => {
if(hasSyntacticModifier(m, ModifierFlags.Static)) return;
if(m.kind === SyntaxKind.SemicolonClassElement || m.kind === SyntaxKind.ClassStaticBlockDeclaration) return;
addLocalOnlyDeclaration(getMemberName(m), m, getElementFlagsOrThrow(m));
bindTypeExpressions(m)
})
});
withMembers(classSymbol, () => {
classDeclaration.members.forEach(m => {
if(!hasSyntacticModifier(m, ModifierFlags.Static)) return;
if(m.kind === SyntaxKind.SemicolonClassElement
|| m.kind === SyntaxKind.ClassStaticBlockDeclaration) return;
addLocalOnlyDeclaration(getMemberName(m), m, getElementFlagsOrThrow(m));
bindTypeExpressions(m)
})
}, "exports");
}
})
}
}
}
function isExternalModuleWorker(file: SourceFile, isModuleIndicatorNode: (node: Node) => boolean): Node | undefined {
return (
forEach(file.statements, isAnExternalModuleIndicatorNode) || walkTreeForModuleIndicator(file, isModuleIndicatorNode)
// TODO: isolatedDeclarations: find a away to detect commonJS modules
)
}
function isAnExternalModuleIndicatorNode(node: Node) {
return hasSyntacticModifier(node, ModifierFlags.Export)
|| isImportEqualsDeclaration(node) && isExternalModuleReference(node.moduleReference)
|| isImportDeclaration(node)
|| isExportAssignment(node)
|| isExportDeclaration(node) ? node : undefined;
}
function walkTreeForModuleIndicator(node: Node, isModuleIndicatorNode: (node: Node) => boolean) {
function walkTreeForModuleIndicatorWorker(node: Node): Node | undefined {
return isModuleIndicatorNode(node) ? node : forEachChild(node, walkTreeForModuleIndicatorWorker);
}
return walkTreeForModuleIndicatorWorker(node);
}
function isImportMeta(node: Node): boolean {
return isMetaProperty(node) && node.keywordToken === SyntaxKind.ImportKeyword && node.name.escapedText === "meta";
}
/** @internal */
export function getSetExternalModuleIndicator(options: CompilerOptions): [(node: SourceFile) => true | undefined, (node: Node) => boolean] {
function isFileForcedToBeModuleByFormat(file: SourceFile): true | undefined {
// Excludes declaration files - they still require an explicit `export {}` or the like
// for back compat purposes. The only non-declaration files _not_ forced to be a module are `.js` files
// that aren't esm-mode (meaning not in a `type: module` scope).
return ([Extension.Cjs, Extension.Cts, Extension.Mjs, Extension.Mts].some(e => file.fileName.endsWith(e)) ? true : undefined);
}
// TODO: Should this callback be cached?
switch (getEmitModuleDetectionKind(options)) {
case ModuleDetectionKind.Force:
// All non-declaration files are modules, declaration files still do the usual isFileProbablyExternalModule
return [(file) => !file.isDeclarationFile || undefined, () => true];
case ModuleDetectionKind.Legacy:
// Files are modules if they have imports, exports, or import.meta
return [isFileForcedToBeModuleByFormat, isImportMeta];
case ModuleDetectionKind.Auto:
return [
isFileForcedToBeModuleByFormat,
options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev?
n => isImportMeta(n) || isJsxOpeningLikeElement(n) || isJsxFragment(n):
isImportMeta
]
}
}
function getImpliedNodeFormat(fileName: string, options: CompilerOptions, packageJsonFormat: ResolutionMode) {
switch (getEmitModuleResolutionKind(options)) {
case ModuleResolutionKind.Node16:
case ModuleResolutionKind.NodeNext:
return [Extension.Dmts, Extension.Mts, Extension.Mjs].some(e => fileName.endsWith(e)) ? ModuleKind.ESNext :
[Extension.Dcts, Extension.Cts, Extension.Cjs].some(e => fileName.endsWith(e)) ? ModuleKind.CommonJS :
[Extension.Dts, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx].some(e => fileName.endsWith(e)) ? packageJsonFormat :
undefined; // other extensions, like `json` or `tsbuildinfo`, are set as `undefined` here but they should never be fed through the transformer pipeline
default:
return undefined;
}
}
export const enum ModuleInstanceState {
NonInstantiated = 0,
Instantiated = 1,
ConstEnumOnly = 2
}
export function getModuleInstanceState(node: ModuleDeclaration, visited?: Map<number, ModuleInstanceState | undefined>): ModuleInstanceState {
return node.body ? getModuleInstanceStateCached(node.body, visited) : ModuleInstanceState.Instantiated;
}
function getModuleInstanceStateCached(node: Node, visited = new Map<number, ModuleInstanceState | undefined>()) {
const nodeId = getNodeId(node);
if (visited.has(nodeId)) {
return visited.get(nodeId) || ModuleInstanceState.NonInstantiated;
}
visited.set(nodeId, undefined);
const result = getModuleInstanceStateWorker(node, visited);
visited.set(nodeId, result);
return result;
}
function getModuleInstanceStateWorker(node: Node, visited: Map<number, ModuleInstanceState | undefined>): ModuleInstanceState {
// A module is uninstantiated if it contains only
switch (node.kind) {
// 1. interface declarations, type alias declarations
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
return ModuleInstanceState.NonInstantiated;
// 2. const enum declarations
case SyntaxKind.EnumDeclaration:
if (isEnumConst(node as EnumDeclaration)) {
return ModuleInstanceState.ConstEnumOnly;
}
break;
// 3. non-exported import declarations
case SyntaxKind.ImportDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
if (!(hasSyntacticModifier(node, ModifierFlags.Export))) {
return ModuleInstanceState.NonInstantiated;
}
break;
// 4. Export alias declarations pointing at only uninstantiated modules or things uninstantiated modules contain
case SyntaxKind.ExportDeclaration:
const exportDeclaration = node as ExportDeclaration;
if (!exportDeclaration.moduleSpecifier && exportDeclaration.exportClause && exportDeclaration.exportClause.kind === SyntaxKind.NamedExports) {
let state = ModuleInstanceState.NonInstantiated;
for (const specifier of exportDeclaration.exportClause.elements) {
const specifierState = getModuleInstanceStateForAliasTarget(specifier, visited);
if (specifierState > state) {
state = specifierState;
}
if (state === ModuleInstanceState.Instantiated) {
return state;
}
}
return state;
}
break;
// 5. other uninstantiated module declarations.
case SyntaxKind.ModuleBlock: {
let state = ModuleInstanceState.NonInstantiated;
forEachChild(node, n => {
const childState = getModuleInstanceStateCached(n, visited);
switch (childState) {
case ModuleInstanceState.NonInstantiated:
// child is non-instantiated - continue searching
return;
case ModuleInstanceState.ConstEnumOnly:
// child is const enum only - record state and continue searching
state = ModuleInstanceState.ConstEnumOnly;
return;
case ModuleInstanceState.Instantiated:
// child is instantiated - record state and stop
state = ModuleInstanceState.Instantiated;
return true;
default:
Debug.assertNever(childState);
}
});
return state;
}
case SyntaxKind.ModuleDeclaration:
return getModuleInstanceState(node as ModuleDeclaration, visited);
case SyntaxKind.Identifier:
// Only jsdoc typedef definition can exist in jsdoc namespace, and it should
// be considered the same as type alias
if ((node as Identifier).isInJSDocNamespace) {
return ModuleInstanceState.NonInstantiated;
}
}
return ModuleInstanceState.Instantiated;
}
function getModuleInstanceStateForAliasTarget(specifier: ExportSpecifier, visited: Map<number, ModuleInstanceState | undefined>) {
const name = specifier.propertyName || specifier.name;
let p: Node | undefined = specifier.parent;
while (p) {
if (isBlock(p) || isModuleBlock(p) || isSourceFile(p)) {
const statements = p.statements;
let found: ModuleInstanceState | undefined;
for (const statement of statements) {
if (nodeHasName(statement, name)) {
const state = getModuleInstanceStateCached(statement, visited);
if (found === undefined || state > found) {
found = state;
}
if (found === ModuleInstanceState.Instantiated) {
return found;
}
}
}
if (found !== undefined) {
return found;
}
}
p = p.parent;
}
return ModuleInstanceState.Instantiated; // Couldn't locate, assume could refer to a value
}

View File

@@ -0,0 +1,69 @@
import * as ts from 'typescript'
import { changeExtension } from '../test-runner/tsc-infrastructure/vpath';
import { getDeclarationExtension, getDirectoryPath, getRelativePathFromDirectory, hasExtension, isDeclarationFile, isJavaScriptFile, resolvePath } from './path-utils';
import { EmitHost } from './types';
import { getNodeId } from './utils';
export function createEmitHost(allProjectFiles: string[], tsLibFiles: string[], options: ts.CompilerOptions) {
const getCompilerOptions = () => options;
const getCurrentDirectory = () => ".";
const getCommonSourceDirectory = () => ".";
const getCanonicalFileName = (f: string) => `./${f}`;
const projectFileMap = new Map(allProjectFiles
.map((f) => ({ kind: ts.SyntaxKind.SourceFile, fileName: f } as ts.SourceFile))
.map(f => [f.fileName, getNodeId(f)])
)
const tsLibFileSet = new Set(tsLibFiles);
return {
getSourceFiles() {
throw new Error("Not needed");
// return [sourceFile]
},
getCompilerOptions,
getCurrentDirectory,
getCommonSourceDirectory,
getCanonicalFileName,
getLibFileFromReference(ref) {
if(options.noLib) {
return undefined;
}
if(!tsLibFileSet.has(ref.fileName)) {
return;
}
return {
fileName: ref.fileName,
}
},
getSourceFileFromReference(referencingFile, ref) {
if(ref.fileName.startsWith("node_modules/") || ref.fileName.indexOf("/node_modules/") !== -1) {
return undefined;
}
if(isJavaScriptFile(ref.fileName) && !options.allowJs) {
return;
}
let resolvedFile: string | undefined = resolvePath(getDirectoryPath(referencingFile.fileName), ref.fileName);
let resolvedFileId = projectFileMap.get(resolvedFile)
if(!hasExtension(resolvedFile) && resolvedFileId == undefined) {
[resolvedFile, resolvedFileId] = Object.values(ts.Extension)
.map(e => resolvedFile + e)
.map(f => [f, projectFileMap.get(f)] as const)
.find(([_, id]) => id != undefined) ?? [];
if(!resolvedFile) return undefined;
}
if(!projectFileMap.has(resolvedFile)) {
return undefined;
}
const resolvedDeclarationFile =
isDeclarationFile(resolvedFile) ? resolvedFile :
changeExtension(resolvedFile, getDeclarationExtension(resolvedFile))
return {
fileName: getRelativePathFromDirectory(getDirectoryPath(referencingFile.fileName), resolvedDeclarationFile, false),
id: resolvedFileId,
};
},
} as Partial<EmitHost> as EmitHost
}

View File

@@ -0,0 +1,405 @@
import { Node, VariableDeclaration, PropertyDeclaration, PropertySignature, ParameterDeclaration, Declaration, getCombinedModifierFlags, isParameterPropertyDeclaration, isVariableDeclaration, ModifierFlags, getCombinedNodeFlags, NodeFlags, VariableDeclarationList, isLiteralExpression, isIdentifier, BindingPattern, isSourceFile, SyntaxKind, findAncestor, SourceFile, EntityNameOrEntityNameExpression, isBindingElement, isVariableStatement, SymbolFlags, __String, ImportClause, ImportEqualsDeclaration, ImportSpecifier, NamespaceImport, isFunctionLike, getNameOfDeclaration, DeclarationName, isElementAccessExpression, isPropertyAccessExpression, FunctionLikeDeclaration, CompilerOptions, isGetAccessor, isSetAccessor, ResolutionMode } from "typescript";
import { Debug } from "./debug";
import { BasicSymbol, bindSourceFile } from "./emit-binder";
import { appendIfUnique, emptyArray, every, filter } from "./lang-utils";
import { EmitResolver, LateBoundDeclaration, LateVisibilityPaintedStatement, SymbolAccessibility, SymbolVisibilityResult, _Symbol } from "./types";
import { isBindingPattern, isExternalModuleAugmentation, hasEffectiveModifier, getTextOfNode, hasSyntacticModifier, isInJSFile, isLateVisibilityPaintedStatement, isThisIdentifier, getFirstIdentifier, isPartOfTypeNode, AnyImportSyntax, hasDynamicName, skipParentheses, nodeIsPresent, isAmbientDeclaration } from "./utils";
export function createEmitResolver(file: SourceFile, options: CompilerOptions, packageModuleType: ResolutionMode) {
const { getNodeLinks, resolveName } = bindSourceFile(file, options, packageModuleType);
function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean {
if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) {
// TODO: Make sure this is a valid approximation for literal types
return !node.type && 'initializer' in node && !!node.initializer && isLiteralExpression(node.initializer);
// Original TS version
// return isFreshLiteralType(getTypeOfSymbol(getSymbolOfNode(node)));
}
return false;
}
function isIdentifierComputedName(name: DeclarationName | undefined): boolean {
if (!name) return false;
if (!(name.kind === SyntaxKind.ComputedPropertyName || name.kind === SyntaxKind.ElementAccessExpression)) {
return false;
}
let expr= isElementAccessExpression(name) ? skipParentheses(name.argumentExpression) : name.expression;
while(isPropertyAccessExpression(expr)) {
expr = expr.expression;
}
return isIdentifier(expr);
}
return {
isDeclarationVisible,
isLiteralConstDeclaration,
createLiteralConstValue(node) {
return 'initializer' in node && node.initializer;
},
isLateBound(node): node is LateBoundDeclaration {
const name = getNameOfDeclaration(node);
return !hasDynamicName(node) || isIdentifierComputedName(name)
},
isImplementationOfOverload(node) {
function getSignaturesOfSymbol(symbol: BasicSymbol | undefined): Node[] {
if(!symbol) return emptyArray;
if(symbol.signatureDeclarations) return symbol.signatureDeclarations;
if (!symbol || !symbol.declarations) return (symbol.signatureDeclarations = emptyArray);
const result: Node[] = symbol.signatureDeclarations = [];
for (let i = 0; i < symbol.declarations.length; i++) {
const decl = symbol.declarations[i];
if (!isFunctionLike(decl) || isGetAccessor(decl) || isSetAccessor(decl)) {
// If non methods got merged in a class member bail with an empty array
// This is TS error behavior and we want to preserve iot as much as possible
// if(isClassElement(decl)) {
// return emptyArray;
// }
continue;
};
// Don't include signature if node is the implementation of an overloaded function. A node is considered
// an implementation node if it has a body and the previous node is of the same kind and immediately
// precedes the implementation node (i.e. has the same parent and ends where the implementation starts).
if (i > 0 && (decl as FunctionLikeDeclaration).body) {
const previous = symbol.declarations[i - 1];
if (decl.parent === previous.parent && decl.kind === previous.kind && decl.pos === previous.end) {
continue;
}
}
// If this is a function or method declaration, get the signature from the @type tag for the sake of optional parameters.
// Exclude contextually-typed kinds because we already apply the @type tag to the context, plus applying it here to the initializer would supress checks that the two are compatible.
result.push(decl);
}
return result;
}
if (nodeIsPresent((node as FunctionLikeDeclaration).body)) {
if (isGetAccessor(node) || isSetAccessor(node)) return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures
const symbol = node.symbol;
const signaturesOfSymbol = getSignaturesOfSymbol(symbol as unknown as BasicSymbol);
// If this function body corresponds to function with multiple signature, it is implementation of overload
// e.g.: function foo(a: string): string;
// function foo(a: number): number;
// function foo(a: any) { // This is implementation of the overloads
// return a;
// }
return signaturesOfSymbol.length > 1 ||
// If there is single signature for the symbol, it is overload if that signature isn't coming from the node
// e.g.: function foo(a: string): string;
// function foo(a: any) { // This is implementation of the overloads
// return a;
// }
(signaturesOfSymbol.length === 1 && signaturesOfSymbol[0] !== node);
}
return false;
},
isOptionalParameter(parameter) {
const signature = parameter.parent;
const paramIndex = signature.parameters.indexOf(parameter);
Debug.assert(paramIndex != -1);
if(parameter.questionToken) return true;
if(parameter.dotDotDotToken) return !!parameter.initializer;
for(let i = paramIndex; i< signature.parameters.length; i++) {
const p = signature.parameters[i];
if(!p.questionToken && !p.initializer && !p.dotDotDotToken) {
return false;
}
}
return true;
},
isEntityNameVisible,
getTypeReferenceDirectivesForEntityName(name) {
return undefined;
},
isExpandoFunctionDeclaration(){
// Always return false, we don' support expando functions in isolatedDeclarations
return false;
},
getSymbolOfExternalModuleSpecifier() {
return undefined;
},
isImportRequiredByAugmentation() {
return false;
},
} as Partial<EmitResolver> as EmitResolver
function isDeclarationReadonly(declaration: Declaration): boolean {
return !!(getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration, declaration.parent));
}
/** @internal */
function isVarConst(node: VariableDeclaration | VariableDeclarationList): boolean {
return !!(getCombinedNodeFlags(node) & NodeFlags.Const);
}
function isDeclarationVisible(node: Node): boolean {
if (node) {
const links = getNodeLinks(node);
if (links.isVisible === undefined) {
links.isVisible = links.symbol?.isVisible ?? !!determineIfDeclarationIsVisible();
}
return links.isVisible;
}
return false;
function determineIfDeclarationIsVisible() {
switch (node.kind) {
case SyntaxKind.JSDocCallbackTag:
case SyntaxKind.JSDocTypedefTag:
case SyntaxKind.JSDocEnumTag:
// Top-level jsdoc type aliases are considered exported
// First parent is comment node, second is hosting declaration or token; we only care about those tokens or declarations whose parent is a source file
return !!(node.parent && node.parent.parent && node.parent.parent.parent && isSourceFile(node.parent.parent.parent));
case SyntaxKind.BindingElement:
return isDeclarationVisible(node.parent.parent);
case SyntaxKind.VariableDeclaration:
if (isBindingPattern((node as VariableDeclaration).name) &&
!((node as VariableDeclaration).name as BindingPattern).elements.length) {
// If the binding pattern is empty, this variable declaration is not visible
return false;
}
// falls through
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
// external module augmentation is always visible
if (isExternalModuleAugmentation(node)) {
return true;
}
const parent = getDeclarationContainer(node);
// If the node is not exported or it is not ambient module element (except import declaration)
if (!(getCombinedModifierFlags(node as Declaration) & ModifierFlags.Export) &&
!(node.kind !== SyntaxKind.ImportEqualsDeclaration && parent.kind !== SyntaxKind.SourceFile && isAmbientDeclaration(parent))) {
return isGlobalSourceFile(parent);
}
// Exported members/ambient module elements (exception import declaration) are visible if parent is visible
return isDeclarationVisible(parent);
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
if (hasEffectiveModifier(node, ModifierFlags.Private | ModifierFlags.Protected)) {
// Private/protected properties/methods are not visible
return false;
}
// Public properties/methods are visible if its parents are visible, so:
// falls through
case SyntaxKind.Constructor:
case SyntaxKind.ConstructSignature:
case SyntaxKind.CallSignature:
case SyntaxKind.IndexSignature:
case SyntaxKind.Parameter:
case SyntaxKind.ModuleBlock:
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.TypeLiteral:
case SyntaxKind.TypeReference:
case SyntaxKind.ArrayType:
case SyntaxKind.TupleType:
case SyntaxKind.UnionType:
case SyntaxKind.IntersectionType:
case SyntaxKind.ParenthesizedType:
case SyntaxKind.NamedTupleMember:
return isDeclarationVisible(node.parent);
// Default binding, import specifier and namespace import is visible
// only on demand so by default it is not visible
case SyntaxKind.ImportClause:
case SyntaxKind.NamespaceImport:
case SyntaxKind.ImportSpecifier:
return false;
// Type parameters are always visible
case SyntaxKind.TypeParameter:
// Source file and namespace export are always visible
// falls through
case SyntaxKind.SourceFile:
case SyntaxKind.NamespaceExportDeclaration:
return true;
// Export assignments do not create name bindings outside the module
case SyntaxKind.ExportAssignment:
return false;
default:
return false;
}
}
}
function hasVisibleDeclarations(symbol: BasicSymbol, shouldComputeAliasToMakeVisible: boolean): SymbolVisibilityResult | undefined {
let aliasesToMakeVisible: LateVisibilityPaintedStatement[] | undefined;
if (!every(filter(symbol.declarations, d => d.kind !== SyntaxKind.Identifier), getIsDeclarationVisible)) {
return undefined;
}
return { accessibility: SymbolAccessibility.Accessible, aliasesToMakeVisible };
function getIsDeclarationVisible(declaration: Declaration) {
if (!isDeclarationVisible(declaration)) {
// Mark the unexported alias as visible if its parent is visible
// because these kind of aliases can be used to name types in declaration file
const anyImportSyntax = getAnyImportSyntax(declaration);
if (anyImportSyntax &&
!hasSyntacticModifier(anyImportSyntax, ModifierFlags.Export) && // import clause without export
isDeclarationVisible(anyImportSyntax.parent)) {
return addVisibleAlias(declaration, anyImportSyntax);
}
else if (isVariableDeclaration(declaration) && isVariableStatement(declaration.parent.parent) &&
!hasSyntacticModifier(declaration.parent.parent, ModifierFlags.Export) && // unexported variable statement
isDeclarationVisible(declaration.parent.parent.parent)) {
return addVisibleAlias(declaration, declaration.parent.parent);
}
else if (isLateVisibilityPaintedStatement(declaration) // unexported top-level statement
&& !hasSyntacticModifier(declaration, ModifierFlags.Export)
&& isDeclarationVisible(declaration.parent)) {
return addVisibleAlias(declaration, declaration);
}
else if (isBindingElement(declaration)) {
if (symbol.flags & SymbolFlags.Alias && isInJSFile(declaration) && declaration.parent?.parent // exported import-like top-level JS require statement
&& isVariableDeclaration(declaration.parent.parent)
&& declaration.parent.parent.parent?.parent && isVariableStatement(declaration.parent.parent.parent.parent)
&& !hasSyntacticModifier(declaration.parent.parent.parent.parent, ModifierFlags.Export)
&& declaration.parent.parent.parent.parent.parent // check if the thing containing the variable statement is visible (ie, the file)
&& isDeclarationVisible(declaration.parent.parent.parent.parent.parent)) {
return addVisibleAlias(declaration, declaration.parent.parent.parent.parent);
}
else if (symbol.flags & SymbolFlags.BlockScopedVariable) {
const variableStatement = findAncestor(declaration, isVariableStatement)!;
if (hasSyntacticModifier(variableStatement, ModifierFlags.Export)) {
return true;
}
if (!isDeclarationVisible(variableStatement.parent)) {
return false;
}
return addVisibleAlias(declaration, variableStatement);
}
}
// Declaration is not visible
return false;
}
return true;
}
function addVisibleAlias(declaration: Declaration, aliasingStatement: LateVisibilityPaintedStatement) {
// In function "buildTypeDisplay" where we decide whether to write type-alias or serialize types,
// we want to just check if type- alias is accessible or not but we don't care about emitting those alias at that time
// since we will do the emitting later in trackSymbol.
if (shouldComputeAliasToMakeVisible) {
getNodeLinks(declaration).isVisible = true;
aliasesToMakeVisible = appendIfUnique(aliasesToMakeVisible, aliasingStatement);
}
return true;
}
}
function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult {
// get symbol of the first identifier of the entityName
let meaning: SymbolFlags;
if (entityName.parent.kind === SyntaxKind.TypeQuery ||
entityName.parent.kind === SyntaxKind.ExpressionWithTypeArguments && !isPartOfTypeNode(entityName.parent) ||
entityName.parent.kind === SyntaxKind.ComputedPropertyName) {
// Typeof value
meaning = SymbolFlags.Value | SymbolFlags.ExportValue;
}
else if (entityName.kind === SyntaxKind.QualifiedName || entityName.kind === SyntaxKind.PropertyAccessExpression ||
entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration) {
// Left identifier from type reference or TypeAlias
// Entity name of the import declaration
meaning = SymbolFlags.Namespace;
}
else {
// Type Reference or TypeAlias entity = Identifier
meaning = SymbolFlags.Type;
}
const firstIdentifier = getFirstIdentifier(entityName);
const symbol = resolveName(enclosingDeclaration, firstIdentifier.escapedText, meaning);
if (symbol && symbol.flags & SymbolFlags.TypeParameter && meaning & SymbolFlags.Type) {
return { accessibility: SymbolAccessibility.Accessible };
}
if (!symbol && isThisIdentifier(firstIdentifier)
// TODO: isolatedDeclarations: Just assume this is accessible
// && isSymbolAccessible(getSymbolOfNode(getThisContainer(firstIdentifier, /*includeArrowFunctions*/ false)), firstIdentifier, meaning, /*computeAliases*/ false).accessibility === SymbolAccessibility.Accessible
) {
// TODO: isolatedDeclarations: Is this ever hit for declarations ?
// debugger;
return { accessibility: SymbolAccessibility.Accessible };
}
// Verify if the symbol is accessible
return (symbol && hasVisibleDeclarations(symbol, /*shouldComputeAliasToMakeVisible*/ true)) || {
// TODO: isolatedDeclarations: In TS this would be an inaccessible symbol, but TS will give errors we just transform
// We don't actually keep enough information for full accessibility,
// We just do this to mark accessible imports
accessibility: SymbolAccessibility.Accessible,
errorSymbolName: getTextOfNode(firstIdentifier),
errorNode: firstIdentifier
};
}
}
/** @internal */
export function getRootDeclaration(node: Node): Node {
while (node.kind === SyntaxKind.BindingElement) {
node = node.parent.parent;
}
return node;
}
function getDeclarationContainer(node: Node): Node {
return findAncestor(getRootDeclaration(node), node => {
switch (node.kind) {
case SyntaxKind.VariableDeclaration:
case SyntaxKind.VariableDeclarationList:
case SyntaxKind.ImportSpecifier:
case SyntaxKind.NamedImports:
case SyntaxKind.NamespaceImport:
case SyntaxKind.ImportClause:
return false;
default:
return true;
}
})!.parent;
}
// Isolated declarations never support global source files.
function isGlobalSourceFile(node: Node) {
return isSourceFile(node) && !isExternalModule(node);
}
export function isExternalModule(file: SourceFile): boolean {
return file.externalModuleIndicator !== undefined;
}
function getAnyImportSyntax(node: Node): AnyImportSyntax | undefined {
switch (node.kind) {
case SyntaxKind.ImportEqualsDeclaration:
return node as ImportEqualsDeclaration;
case SyntaxKind.ImportClause:
return (node as ImportClause).parent;
case SyntaxKind.NamespaceImport:
return (node as NamespaceImport).parent.parent;
case SyntaxKind.ImportSpecifier:
return (node as ImportSpecifier).parent.parent.parent;
default:
return undefined;
}
}

View File

@@ -0,0 +1,945 @@
import { MapLike, SortedArray, SortedReadonlyArray } from "typescript";
export type Mutable<T extends object> = { -readonly [K in keyof T]: T[K] };
/** @internal */
export type EqualityComparer<T> = (a: T, b: T) => boolean;
/** @internal */
export type Comparer<T> = (a: T, b: T) => Comparison;
export function forEach<T, U>(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined {
if (array) {
for (let i = 0; i < array.length; i++) {
const result = callback(array[i], i);
if (result) {
return result;
}
}
}
return undefined;
}
/** @internal */
export function concatenate<T>(array1: T[], array2: T[]): T[];
/** @internal */
export function concatenate<T>(array1: readonly T[], array2: readonly T[]): readonly T[];
/** @internal */
export function concatenate<T>(array1: T[] | undefined, array2: T[] | undefined): T[];
/** @internal */
export function concatenate<T>(array1: readonly T[] | undefined, array2: readonly T[] | undefined): readonly T[];
/** @internal */
export function concatenate<T>(array1: undefined | readonly T[], array2: undefined | readonly T[]): undefined | T[] | readonly T[] {
if (!some(array2)) return array1;
if (!some(array1)) return array2;
return [...array1, ...array2];
}
/** @internal */
export function some<T>(array: readonly T[] | undefined): array is readonly T[];
/** @internal */
export function some<T>(array: readonly T[] | undefined, predicate: (value: T) => boolean): boolean;
/** @internal */
export function some<T>(array: readonly T[] | undefined, predicate?: (value: T) => boolean): boolean {
if (array) {
if (predicate) {
for (const v of array) {
if (predicate(v)) {
return true;
}
}
}
else {
return array.length > 0;
}
}
return false;
}
/** @internal */
export function stringContains(str: string, substring: string): boolean {
return str.indexOf(substring) !== -1;
}
/**
* Filters an array by a predicate function. Returns the same array instance if the predicate is
* true for all elements, otherwise returns a new array instance containing the filtered subset.
*
* @internal
*/
export function filter<T, U extends T>(array: T[], f: (x: T) => x is U): U[];
/** @internal */
export function filter<T>(array: T[], f: (x: T) => boolean): T[];
/** @internal */
export function filter<T, U extends T>(array: readonly T[], f: (x: T) => x is U): readonly U[];
/** @internal */
export function filter<T, U extends T>(array: readonly T[], f: (x: T) => boolean): readonly T[];
/** @internal */
export function filter<T, U extends T>(array: T[] | undefined, f: (x: T) => x is U): U[] | undefined;
/** @internal */
export function filter<T>(array: T[] | undefined, f: (x: T) => boolean): T[] | undefined;
/** @internal */
export function filter<T, U extends T>(array: readonly T[] | undefined, f: (x: T) => x is U): readonly U[] | undefined;
/** @internal */
export function filter<T, U extends T>(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined;
/** @internal */
export function filter<T>(array: readonly T[] | undefined, f: (x: T) => boolean): readonly T[] | undefined {
if (array) {
const len = array.length;
let i = 0;
while (i < len && f(array[i])) i++;
if (i < len) {
const result = array.slice(0, i);
i++;
while (i < len) {
const item = array[i];
if (f(item)) {
result.push(item);
}
i++;
}
return result;
}
}
return array;
}
/**
* @return Whether the value was added.
*
* @internal
*/
export function pushIfUnique<T>(array: T[], toAdd: T, equalityComparer?: EqualityComparer<T>): boolean {
if (contains(array, toAdd, equalityComparer)) {
return false;
}
else {
array.push(toAdd);
return true;
}
}
/** @internal */
export function contains<T>(array: readonly T[] | undefined, value: T, equalityComparer: EqualityComparer<T> = equateValues): boolean {
if (array) {
for (const v of array) {
if (equalityComparer(v, value)) {
return true;
}
}
}
return false;
}
/** @internal */
export const enum Comparison {
LessThan = -1,
EqualTo = 0,
GreaterThan = 1
}
export function equateValues<T>(a: T, b: T) {
return a === b;
}
/** @internal */
export function length(array: readonly any[] | undefined): number {
return array ? array.length : 0;
}
/**
* Flattens an array containing a mix of array or non-array elements.
*
* @param array The array to flatten.
*
* @internal
*/
export function flatten<T>(array: T[][] | readonly (T | readonly T[] | undefined)[]): T[] {
const result: T[] = [];
for (const v of array) {
if (v) {
if (isArray(v)) {
addRange(result, v);
}
else {
result.push(v);
}
}
}
return result;
}
/**
* Tests whether a value is an array.
*
* @internal
*/
export function isArray(value: any): value is readonly unknown[] {
return Array.isArray ? Array.isArray(value) : value instanceof Array;
}
/**
* Appends a range of value to an array, returning the array.
*
* @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array
* is created if `value` was appended.
* @param from The values to append to the array. If `from` is `undefined`, nothing is
* appended. If an element of `from` is `undefined`, that element is not appended.
* @param start The offset in `from` at which to start copying values.
* @param end The offset in `from` at which to stop copying values (non-inclusive).
*
* @internal
*/
export function addRange<T>(to: T[], from: readonly T[] | undefined, start?: number, end?: number): T[];
/** @internal */
export function addRange<T>(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined;
/** @internal */
export function addRange<T>(to: T[] | undefined, from: readonly T[] | undefined, start?: number, end?: number): T[] | undefined {
if (from === undefined || from.length === 0) return to;
if (to === undefined) return from.slice(start, end);
start = start === undefined ? 0 : toOffset(from, start);
end = end === undefined ? from.length : toOffset(from, end);
for (let i = start; i < end && i < from.length; i++) {
if (from[i] !== undefined) {
to.push(from[i]);
}
}
return to;
}
/**
* Gets the actual offset into an array for a relative offset. Negative offsets indicate a
* position offset from the end of the array.
*/
function toOffset(array: readonly any[], offset: number) {
return offset < 0 ? array.length + offset : offset;
}
/** @internal */
export function map<T, U>(array: readonly T[], f: (x: T, i: number) => U): U[];
/** @internal */
export function map<T, U>(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined;
/** @internal */
export function map<T, U>(array: readonly T[] | undefined, f: (x: T, i: number) => U): U[] | undefined {
let result: U[] | undefined;
if (array) {
result = [];
for (let i = 0; i < array.length; i++) {
result.push(f(array[i], i));
}
}
return result;
}
/**
* Compacts an array, removing any falsey elements.
*
* @internal
*/
export function compact<T>(array: (T | undefined | null | false | 0 | "")[]): T[];
/** @internal */
export function compact<T>(array: readonly (T | undefined | null | false | 0 | "")[]): readonly T[];
// ESLint thinks these can be combined with the above - they cannot; they'd produce higher-priority inferences and prevent the falsey types from being stripped
/** @internal */
export function compact<T>(array: T[]): T[]; // eslint-disable-line @typescript-eslint/unified-signatures
/** @internal */
export function compact<T>(array: readonly T[]): readonly T[]; // eslint-disable-line @typescript-eslint/unified-signatures
/** @internal */
export function compact<T>(array: T[]): T[] {
let result: T[] | undefined;
if (array) {
for (let i = 0; i < array.length; i++) {
const v = array[i];
if (result || !v) {
if (!result) {
result = array.slice(0, i);
}
if (v) {
result.push(v);
}
}
}
}
return result || array;
}
/**
* Maps an array. If the mapped value is an array, it is spread into the result.
*
* @param array The array to map.
* @param mapfn The callback used to map the result into one or more values.
*
* @internal
*/
export function flatMap<T, U>(array: readonly T[] | undefined, mapfn: (x: T, i: number) => U | readonly U[] | undefined): readonly U[] {
let result: U[] | undefined;
if (array) {
for (let i = 0; i < array.length; i++) {
const v = mapfn(array[i], i);
if (v) {
if (isArray(v)) {
result = addRange(result, v);
}
else {
result = append(result, v);
}
}
}
}
return result || emptyArray;
}
/** @internal */
export const emptyArray: never[] = [] as never[];
/**
* Appends a value to an array, returning the array.
*
* @param to The array to which `value` is to be appended. If `to` is `undefined`, a new array
* is created if `value` was appended.
* @param value The value to append to the array. If `value` is `undefined`, nothing is
* appended.
*
* @internal
*/
export function append<TArray extends any[] | undefined, TValue extends NonNullable<TArray>[number] | undefined>(to: TArray, value: TValue): [undefined, undefined] extends [TArray, TValue] ? TArray : NonNullable<TArray>[number][];
/** @internal */
export function append<T>(to: T[], value: T | undefined): T[];
/** @internal */
export function append<T>(to: T[] | undefined, value: T): T[];
/** @internal */
export function append<T>(to: T[] | undefined, value: T | undefined): T[] | undefined;
/** @internal */
export function append<T>(to: Push<T>, value: T | undefined): void;
/** @internal */
export function append<T>(to: T[], value: T | undefined): T[] | undefined {
if (value === undefined) return to;
if (to === undefined) return [value];
to.push(value);
return to;
}
/** Array that is only intended to be pushed to, never read. */
export interface Push<T> {
push(...values: T[]): void;
/** @internal */ readonly length: number;
}
/**
* Stable sort of an array. Elements equal to each other maintain their relative position in the array.
*
* @internal
*/
export function stableSort<T>(array: readonly T[], comparer: Comparer<T>): SortedReadonlyArray<T> {
const indices = indicesOf(array);
stableSortIndices(array, indices, comparer);
return indices.map(i => array[i]) as SortedArray<T> as SortedReadonlyArray<T>;
}
function selectIndex(_: unknown, i: number) {
return i;
}
function stableSortIndices<T>(array: readonly T[], indices: number[], comparer: Comparer<T>) {
// sort indices by value then position
indices.sort((x, y) => comparer(array[x], array[y]) || compareValues(x, y));
}
/**
* Works like Array.prototype.find, returning `undefined` if no element satisfying the predicate is found.
*
* @internal
*/
export function find<T, U extends T>(array: readonly T[] | undefined, predicate: (element: T, index: number) => element is U, startIndex?: number): U | undefined;
/** @internal */
export function find<T>(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined;
/** @internal */
export function find<T>(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): T | undefined {
if (array === undefined) return undefined;
for (let i = startIndex ?? 0; i < array.length; i++) {
const value = array[i];
if (predicate(value, i)) {
return value;
}
}
return undefined;
}
/**
* Returns the last element of an array if non-empty, `undefined` otherwise.
*
* @internal
*/
export function lastOrUndefined<T>(array: readonly T[] | undefined): T | undefined {
return array === undefined || array.length === 0 ? undefined : array[array.length - 1];
}
/** @internal */
export function last<T>(array: readonly T[]): T {
return array[array.length - 1];
}
/** @internal */
export function indicesOf(array: readonly unknown[]): number[] {
return array.map(selectIndex);
}
/** @internal */
export function findLastIndex<T>(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): number {
if (array === undefined) return -1;
for (let i = startIndex ?? array.length - 1; i >= 0; i--) {
if (predicate(array[i], i)) {
return i;
}
}
return -1;
}
/**
* Compare the equality of two strings using a case-sensitive ordinal comparison.
*
* Case-sensitive comparisons compare both strings one code-point at a time using the integer
* value of each code-point after applying `toUpperCase` to each string. We always map both
* strings to their upper-case form as some unicode characters do not properly round-trip to
* lowercase (such as `ẞ` (German sharp capital s)).
*
* @internal
*/
export function equateStringsCaseInsensitive(a: string, b: string) {
return a === b
|| a !== undefined
&& b !== undefined
&& a.toUpperCase() === b.toUpperCase();
}
/**
* Compare the equality of two strings using a case-sensitive ordinal comparison.
*
* Case-sensitive comparisons compare both strings one code-point at a time using the
* integer value of each code-point.
*
* @internal
*/
export function equateStringsCaseSensitive(a: string, b: string) {
return equateValues(a, b);
}
function compareComparableValues(a: string | undefined, b: string | undefined): Comparison;
function compareComparableValues(a: number | undefined, b: number | undefined): Comparison;
function compareComparableValues(a: string | number | undefined, b: string | number | undefined) {
return a === b ? Comparison.EqualTo :
a === undefined ? Comparison.LessThan :
b === undefined ? Comparison.GreaterThan :
a < b ? Comparison.LessThan :
Comparison.GreaterThan;
}
/**
* Compare two numeric values for their order relative to each other.
* To compare strings, use any of the `compareStrings` functions.
*
* @internal
*/
export function compareValues(a: number | undefined, b: number | undefined): Comparison {
return compareComparableValues(a, b);
}
/**
* Compare two strings using a case-insensitive ordinal comparison.
*
* Ordinal comparisons are based on the difference between the unicode code points of both
* strings. Characters with multiple unicode representations are considered unequal. Ordinal
* comparisons provide predictable ordering, but place "a" after "B".
*
* Case-insensitive comparisons compare both strings one code-point at a time using the integer
* value of each code-point after applying `toUpperCase` to each string. We always map both
* strings to their upper-case form as some unicode characters do not properly round-trip to
* lowercase (such as `ẞ` (German sharp capital s)).
*
* @internal
*/
export function compareStringsCaseInsensitive(a: string, b: string) {
if (a === b) return Comparison.EqualTo;
if (a === undefined) return Comparison.LessThan;
if (b === undefined) return Comparison.GreaterThan;
a = a.toUpperCase();
b = b.toUpperCase();
return a < b ? Comparison.LessThan : a > b ? Comparison.GreaterThan : Comparison.EqualTo;
}
/**
* Compare two strings using a case-sensitive ordinal comparison.
*
* Ordinal comparisons are based on the difference between the unicode code points of both
* strings. Characters with multiple unicode representations are considered unequal. Ordinal
* comparisons provide predictable ordering, but place "a" after "B".
*
* Case-sensitive comparisons compare both strings one code-point at a time using the integer
* value of each code-point.
*
* @internal
*/
export function compareStringsCaseSensitive(a: string | undefined, b: string | undefined): Comparison {
return compareComparableValues(a, b);
}
/** @internal */
export function getStringComparer(ignoreCase?: boolean) {
return ignoreCase ? compareStringsCaseInsensitive : compareStringsCaseSensitive;
}
/**
* Iterates through `array` by index and performs the callback on each element of array until the callback
* returns a falsey value, then returns false.
* If no such value is found, the callback is applied to each element of array and `true` is returned.
*
* @internal
*/
export function every<T>(array: readonly T[] | undefined, callback: (element: T, index: number) => boolean): boolean {
if (array) {
for (let i = 0; i < array.length; i++) {
if (!callback(array[i], i)) {
return false;
}
}
}
return true;
}
/** @internal */
export function mapDefined<T, U>(array: readonly T[] | undefined, mapFn: (x: T, i: number) => U | undefined): U[] {
const result: U[] = [];
if (array) {
for (let i = 0; i < array.length; i++) {
const mapped = mapFn(array[i], i);
if (mapped !== undefined) {
result.push(mapped);
}
}
}
return result;
}
/**
* Shims `Array.from`.
*
* @internal
*/
export function arrayFrom<T, U>(iterator: Iterator<T> | IterableIterator<T>, map: (t: T) => U): U[];
/** @internal */
export function arrayFrom<T>(iterator: Iterator<T> | IterableIterator<T>): T[];
/** @internal */
export function arrayFrom<T, U>(iterator: Iterator<T> | IterableIterator<T>, map?: (t: T) => U): (T | U)[] {
const result: (T | U)[] = [];
for (let iterResult = iterator.next(); !iterResult.done; iterResult = iterator.next()) {
result.push(map ? map(iterResult.value) : iterResult.value);
}
return result;
}
/** @internal */
export function startsWith(str: string, prefix: string): boolean {
return str.lastIndexOf(prefix, 0) === 0;
}
/** @internal */
export function endsWith(str: string, suffix: string): boolean {
const expectedPos = str.length - suffix.length;
return expectedPos >= 0 && str.indexOf(suffix, expectedPos) === expectedPos;
}
/** @internal */
export function removeSuffix(str: string, suffix: string): string {
return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : str;
}
/** @internal */
export function tryRemoveSuffix(str: string, suffix: string): string | undefined {
return endsWith(str, suffix) ? str.slice(0, str.length - suffix.length) : undefined;
}
/**
* Returns lower case string
*
* @internal
*/
export function toLowerCase(x: string) {
return x.toLowerCase();
}
/**
* Returns its argument.
*
* @internal
*/
export function identity<T>(x: T) {
return x;
}
/**
* Remove an item from an array, moving everything to its right one space left.
*
* @internal
*/
export function orderedRemoveItem<T>(array: T[], item: T): boolean {
for (let i = 0; i < array.length; i++) {
if (array[i] === item) {
orderedRemoveItemAt(array, i);
return true;
}
}
return false;
}
/**
* Remove an item by index from an array, moving everything to its right one space left.
*
* @internal
*/
export function orderedRemoveItemAt<T>(array: T[], index: number): void {
// This seems to be faster than either `array.splice(i, 1)` or `array.copyWithin(i, i+ 1)`.
for (let i = index; i < array.length - 1; i++) {
array[i] = array[i + 1];
}
array.pop();
}
export function getEntries<T>(obj: MapLike<T>): [string, T][] {
return obj ? _entries(obj) : [];
}
const _entries = Object.entries || (<T>(obj: MapLike<T>) => {
const keys = getOwnKeys(obj);
const result: [string, T][] = Array(keys.length);
for (let i = 0; i < keys.length; i++) {
result[i] = [keys[i], obj[keys[i]]];
}
return result;
});
const hasOwnProperty = Object.prototype.hasOwnProperty;
/**
* Gets the owned, enumerable property keys of a map-like.
*
* @internal
*/
export function getOwnKeys<T>(map: MapLike<T>): string[] {
const keys: string[] = [];
for (const key in map) {
if (hasOwnProperty.call(map, key)) {
keys.push(key);
}
}
return keys;
}
/** @internal */
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined;
/** @internal */
export function tryCast<T>(value: T, test: (value: T) => boolean): T | undefined;
/** @internal */
export function tryCast<T>(value: T, test: (value: T) => boolean): T | undefined {
return value !== undefined && test(value) ? value : undefined;
}
/**
* Like `forEach`, but suitable for use with numbers and strings (which may be falsy).
*
* @internal
*/
export function firstDefined<T, U>(array: readonly T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined {
if (array === undefined) {
return undefined;
}
for (let i = 0; i < array.length; i++) {
const result = callback(array[i], i);
if (result !== undefined) {
return result;
}
}
return undefined;
}
/**
* True is greater than false.
*
* @internal
*/
export function compareBooleans(a: boolean, b: boolean): Comparison {
return compareValues(a ? 1 : 0, b ? 1 : 0);
}
/**
* Tests whether a value is string
*
* @internal
*/
export function isString(text: unknown): text is string {
return typeof text === "string";
}
/** @internal */
export function isNumber(x: unknown): x is number {
return typeof x === "number";
}
/**
* patternOrStrings contains both patterns (containing "*") and regular strings.
* Return an exact match if possible, or a pattern match, or undefined.
* (These are verified by verifyCompilerOptions to have 0 or 1 "*" characters.)
*
* @internal
*/
export function matchPatternOrExact(patternOrStrings: readonly (string | Pattern)[], candidate: string): string | Pattern | undefined {
const patterns: Pattern[] = [];
for (const patternOrString of patternOrStrings) {
if (patternOrString === candidate) {
return candidate;
}
if (!isString(patternOrString)) {
patterns.push(patternOrString);
}
}
return findBestPatternMatch(patterns, _ => _, candidate);
}
/**
* Represents a "prefix*suffix" pattern.
*
* @internal
*/
export interface Pattern {
prefix: string;
suffix: string;
}
/**
* Return the object corresponding to the best pattern to match `candidate`.
*
* @internal
*/
export function findBestPatternMatch<T>(values: readonly T[], getPattern: (value: T) => Pattern, candidate: string): T | undefined {
let matchedValue: T | undefined;
// use length of prefix as betterness criteria
let longestMatchPrefixLength = -1;
for (const v of values) {
const pattern = getPattern(v);
if (isPatternMatch(pattern, candidate) && pattern.prefix.length > longestMatchPrefixLength) {
longestMatchPrefixLength = pattern.prefix.length;
matchedValue = v;
}
}
return matchedValue;
}
/** @internal */
export function isPatternMatch({ prefix, suffix }: Pattern, candidate: string) {
return candidate.length >= prefix.length + suffix.length &&
startsWith(candidate, prefix) &&
endsWith(candidate, suffix);
}
/** @internal */
export function tryParsePatterns(paths: MapLike<string[]>): (string | Pattern)[] {
return mapDefined(getOwnKeys(paths), path => tryParsePattern(path));
}
/**
* Returns the input if there are no stars, a pattern if there is exactly one,
* and undefined if there are more.
*
* @internal
*/
export function tryParsePattern(pattern: string): string | Pattern | undefined {
const indexOfStar = pattern.indexOf("*");
if (indexOfStar === -1) {
return pattern;
}
return pattern.indexOf("*", indexOfStar + 1) !== -1
? undefined
: {
prefix: pattern.substr(0, indexOfStar),
suffix: pattern.substr(indexOfStar + 1)
};
}
/** @internal */
export function min<T>(items: readonly [T, ...T[]], compare: Comparer<T>): T;
/** @internal */
export function min<T>(items: readonly T[], compare: Comparer<T>): T | undefined;
/** @internal */
export function min<T>(items: readonly T[], compare: Comparer<T>): T | undefined {
return reduceLeft(items, (x, y) => compare(x, y) === Comparison.LessThan ? x : y);
}
/** @internal */
export function reduceLeft<T, U>(array: readonly T[] | undefined, f: (memo: U, value: T, i: number) => U, initial: U, start?: number, count?: number): U;
/** @internal */
export function reduceLeft<T>(array: readonly T[], f: (memo: T, value: T, i: number) => T): T | undefined;
/** @internal */
export function reduceLeft<T>(array: T[], f: (memo: T, value: T, i: number) => T, initial?: T, start?: number, count?: number): T | undefined {
if (array && array.length > 0) {
const size = array.length;
if (size > 0) {
let pos = start === undefined || start < 0 ? 0 : start;
const end = count === undefined || pos + count > size - 1 ? size - 1 : pos + count;
let result: T;
if (arguments.length <= 2) {
result = array[pos];
pos++;
}
else {
result = initial!;
}
while (pos <= end) {
result = f(result, array[pos], pos);
pos++;
}
return result;
}
}
return initial;
}
export const trimString = (s: string) => s.trim();
/**
* Unlike `pushIfUnique`, this can take `undefined` as an input, and returns a new array.
*
* @internal
*/
export function appendIfUnique<T>(array: T[] | undefined, toAdd: T, equalityComparer?: EqualityComparer<T>): T[] {
if (array) {
pushIfUnique(array, toAdd, equalityComparer);
return array;
}
else {
return [toAdd];
}
}
export function isNullOrUndefined(x: any): x is null | undefined {
return x === undefined || x === null; // eslint-disable-line no-null/no-null
}
/**
* Performs a binary search, finding the index at which `value` occurs in `array`.
* If no such index is found, returns the 2's-complement of first index at which
* `array[index]` exceeds `value`.
* @param array A sorted array whose first element must be no larger than number
* @param value The value to be searched for in the array.
* @param keySelector A callback used to select the search key from `value` and each element of
* `array`.
* @param keyComparer A callback used to compare two keys in a sorted array.
* @param offset An offset into `array` at which to start the search.
*
* @internal
*/
export function binarySearch<T, U>(array: readonly T[], value: T, keySelector: (v: T) => U, keyComparer: Comparer<U>, offset?: number): number {
return binarySearchKey(array, keySelector(value), keySelector, keyComparer, offset);
}
/**
* Performs a binary search, finding the index at which an object with `key` occurs in `array`.
* If no such index is found, returns the 2's-complement of first index at which
* `array[index]` exceeds `key`.
* @param array A sorted array whose first element must be no larger than number
* @param key The key to be searched for in the array.
* @param keySelector A callback used to select the search key from each element of `array`.
* @param keyComparer A callback used to compare two keys in a sorted array.
* @param offset An offset into `array` at which to start the search.
*
* @internal
*/
export function binarySearchKey<T, U>(array: readonly T[], key: U, keySelector: (v: T, i: number) => U, keyComparer: Comparer<U>, offset?: number): number {
if (!some(array)) {
return -1;
}
let low = offset || 0;
let high = array.length - 1;
while (low <= high) {
const middle = low + ((high - low) >> 1);
const midKey = keySelector(array[middle], middle);
switch (keyComparer(midKey, key)) {
case Comparison.LessThan:
low = middle + 1;
break;
case Comparison.EqualTo:
return middle;
case Comparison.GreaterThan:
high = middle - 1;
break;
}
}
return ~low;
}
/**
* Works like Array.prototype.findIndex, returning `-1` if no element satisfying the predicate is found.
*
* @internal
*/
export function findIndex<T>(array: readonly T[] | undefined, predicate: (element: T, index: number) => boolean, startIndex?: number): number {
if (array === undefined) return -1;
for (let i = startIndex ?? 0; i < array.length; i++) {
if (predicate(array[i], i)) {
return i;
}
}
return -1;
}
/** @internal */
export function clone<T>(object: T): T {
const result: any = {};
for (const id in object) {
if (hasOwnProperty.call(object, id)) {
result[id] = (object as any)[id];
}
}
return result;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
import * as ts from 'typescript'
import { transformDeclarations } from './declaration-emit';
import { createEmitHost } from './emit-host';
import { createEmitResolver } from './emit-resolver';
import { TransformationContext } from './types';
export function transformFile(fileName: string, source: string, allProjectFiles: string[], tsLibFiles: string[], options: ts.CompilerOptions, moduleType: ts.ResolutionMode) {
let sourceFile = ts.createSourceFile(fileName, source, options.target ?? ts.ScriptTarget.ES3, true,
fileName.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
const getCompilerOptions = () => options;
const emitResolver = createEmitResolver(sourceFile, options, moduleType);
const emitHost = createEmitHost(allProjectFiles, tsLibFiles, options);
const diagnostics: ts.Diagnostic[] = []
const x = transformDeclarations({
getEmitHost() {
return emitHost;
},
getEmitResolver() {
return emitResolver;
},
getCompilerOptions,
factory: ts.factory,
addDiagnostic(diag) {
diagnostics.push(diag)
},
} as Partial<TransformationContext> as TransformationContext)
let result = x(sourceFile);
const printer = ts.createPrinter({
onlyPrintJsDocStyle: true,
newLine: options.newLine ?? ts.NewLineKind.CarriageReturnLineFeed,
target: options.target,
} as ts.PrinterOptions)
return {
code: printer.printFile(result),
diagnostics,
};
}

View File

@@ -0,0 +1,451 @@
import { Node, NodeFactory, CompilerOptions, TransformerFactory, TransformationResult, SyntaxKind, VariableDeclaration, FunctionDeclaration, Statement, Identifier, EmitHelper, DiagnosticWithLocation, disposeEmitNodes, getParseTreeNode, SourceFile, isSourceFile, EmitFlags, EmitHint, setEmitFlags, NodeFlags } from "typescript";
import { Debug } from "./debug";
import { some, append } from "./lang-utils";
import { EmitResolver, EmitHost, TransformationContext } from "./types";
import { getEmitFlags, getSourceFileOfNode } from "./utils";
/**
* Transforms an array of SourceFiles by passing them through each transformer.
*
* @param resolver The emit resolver provided by the checker.
* @param host The emit host object used to interact with the file system.
* @param options Compiler options to surface in the `TransformationContext`.
* @param nodes An array of nodes to transform.
* @param transforms An array of `TransformerFactory` callbacks.
* @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files.
*
* @internal
*/
export function transformNodes<T extends Node>(resolver: EmitResolver | undefined, host: EmitHost | undefined, factory: NodeFactory, options: CompilerOptions, nodes: readonly T[], transformers: readonly TransformerFactory<T>[], allowDtsFiles: boolean): TransformationResult<T> {
const enabledSyntaxKindFeatures = new Array<SyntaxKindFeatureFlags>(SyntaxKind.Count);
let lexicalEnvironmentVariableDeclarations: VariableDeclaration[];
let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[];
let lexicalEnvironmentStatements: Statement[];
let lexicalEnvironmentFlags = LexicalEnvironmentFlags.None;
let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = [];
let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = [];
let lexicalEnvironmentStatementsStack: Statement[][] = [];
let lexicalEnvironmentFlagsStack: LexicalEnvironmentFlags[] = [];
let lexicalEnvironmentStackOffset = 0;
let lexicalEnvironmentSuspended = false;
let blockScopedVariableDeclarationsStack: Identifier[][] = [];
let blockScopeStackOffset = 0;
let blockScopedVariableDeclarations: Identifier[];
let emitHelpers: EmitHelper[] | undefined;
let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution;
let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification;
let state = TransformationState.Uninitialized;
const diagnostics: DiagnosticWithLocation[] = [];
// The transformation context is provided to each transformer as part of transformer
// initialization.
const context: TransformationContext = {
factory: factory as TransformationContext['factory'],
getCompilerOptions: () => options,
getEmitResolver: () => resolver!, // TODO: GH#18217
getEmitHost: () => host!, // TODO: GH#18217
startLexicalEnvironment,
suspendLexicalEnvironment,
resumeLexicalEnvironment,
endLexicalEnvironment,
hoistVariableDeclaration,
hoistFunctionDeclaration,
requestEmitHelper,
readEmitHelpers,
enableSubstitution,
enableEmitNotification,
isSubstitutionEnabled,
isEmitNotificationEnabled,
get onSubstituteNode() { return onSubstituteNode; },
set onSubstituteNode(value) {
Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed.");
Debug.assert(value !== undefined, "Value must not be 'undefined'");
onSubstituteNode = value;
},
get onEmitNode() { return onEmitNode; },
set onEmitNode(value) {
Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed.");
Debug.assert(value !== undefined, "Value must not be 'undefined'");
onEmitNode = value;
},
addDiagnostic(diag) {
console.log(diag.messageText);
},
getEmitHelperFactory() {
return factory;
},
};
// Ensure the parse tree is clean before applying transformations
for (const node of nodes) {
disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node)));
}
// Chain together and initialize each transformer.
const transformersWithContext = transformers.map(t => t(context));
const transformation = (node: T): T => {
for (const transform of transformersWithContext) {
node = transform(node);
}
return node;
};
// prevent modification of transformation hooks.
state = TransformationState.Initialized;
// Transform each node.
const transformed: T[] = [];
for (const node of nodes) {
transformed.push((allowDtsFiles ? transformation : transformRoot)(node));
}
// prevent modification of the lexical environment.
state = TransformationState.Completed;
return {
transformed,
substituteNode,
emitNodeWithNotification,
isEmitNotificationEnabled,
dispose,
diagnostics
};
function transformRoot(node: T) {
return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node;
}
/**
* Enables expression substitutions in the pretty printer for the provided SyntaxKind.
*/
function enableSubstitution(kind: SyntaxKind) {
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution;
}
/**
* Determines whether expression substitutions are enabled for the provided node.
*/
function isSubstitutionEnabled(node: Node) {
return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0
&& (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0;
}
/**
* Emits a node with possible substitution.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to emit.
* @param emitCallback The callback used to emit the node or its substitute.
*/
function substituteNode(hint: EmitHint, node: Node) {
Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed.");
return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node;
}
/**
* Enables before/after emit notifications in the pretty printer for the provided SyntaxKind.
*/
function enableEmitNotification(kind: SyntaxKind) {
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications;
}
/**
* Determines whether before/after emit notifications should be raised in the pretty
* printer when it emits a node.
*/
function isEmitNotificationEnabled(node: Node) {
return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0
|| (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0;
}
/**
* Emits a node with possible emit notification.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to emit.
* @param emitCallback The callback used to emit the node.
*/
function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed.");
if (node) {
// TODO: Remove check and unconditionally use onEmitNode when API is breakingly changed
// (see https://github.com/microsoft/TypeScript/pull/36248/files/5062623f39120171b98870c71344b3242eb03d23#r369766739)
if (isEmitNotificationEnabled(node)) {
onEmitNode(hint, node, emitCallback);
}
else {
emitCallback(hint, node);
}
}
}
/**
* Records a hoisted variable declaration for the provided name within a lexical environment.
*/
function hoistVariableDeclaration(name: Identifier): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
const decl = setEmitFlags(factory.createVariableDeclaration(name), EmitFlags.NoNestedSourceMaps);
if (!lexicalEnvironmentVariableDeclarations) {
lexicalEnvironmentVariableDeclarations = [decl];
}
else {
lexicalEnvironmentVariableDeclarations.push(decl);
}
if (lexicalEnvironmentFlags & LexicalEnvironmentFlags.InParameters) {
lexicalEnvironmentFlags |= LexicalEnvironmentFlags.VariablesHoistedInParameters;
}
}
/**
* Records a hoisted function declaration within a lexical environment.
*/
function hoistFunctionDeclaration(func: FunctionDeclaration): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
setEmitFlags(func, EmitFlags.CustomPrologue);
if (!lexicalEnvironmentFunctionDeclarations) {
lexicalEnvironmentFunctionDeclarations = [func];
}
else {
lexicalEnvironmentFunctionDeclarations.push(func);
}
}
/**
* Adds an initialization statement to the top of the lexical environment.
*/
function addInitializationStatement(node: Statement): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
setEmitFlags(node, EmitFlags.CustomPrologue);
if (!lexicalEnvironmentStatements) {
lexicalEnvironmentStatements = [node];
}
else {
lexicalEnvironmentStatements.push(node);
}
}
/**
* Starts a new lexical environment. Any existing hoisted variable or function declarations
* are pushed onto a stack, and the related storage variables are reset.
*/
function startLexicalEnvironment(): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended.");
// Save the current lexical environment. Rather than resizing the array we adjust the
// stack size variable. This allows us to reuse existing array slots we've
// already allocated between transformations to avoid allocation and GC overhead during
// transformation.
lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations;
lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations;
lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentStatements;
lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFlags;
lexicalEnvironmentStackOffset++;
lexicalEnvironmentVariableDeclarations = undefined!;
lexicalEnvironmentFunctionDeclarations = undefined!;
lexicalEnvironmentStatements = undefined!;
lexicalEnvironmentFlags = LexicalEnvironmentFlags.None;
}
/** Suspends the current lexical environment, usually after visiting a parameter list. */
function suspendLexicalEnvironment(): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended.");
lexicalEnvironmentSuspended = true;
}
/** Resumes a suspended lexical environment, usually before visiting a function body. */
function resumeLexicalEnvironment(): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended.");
lexicalEnvironmentSuspended = false;
}
/**
* Ends a lexical environment. The previous set of hoisted declarations are restored and
* any hoisted declarations added in this environment are returned.
*/
function endLexicalEnvironment(): Statement[] | undefined {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended.");
let statements: Statement[] | undefined;
if (lexicalEnvironmentVariableDeclarations ||
lexicalEnvironmentFunctionDeclarations ||
lexicalEnvironmentStatements) {
if (lexicalEnvironmentFunctionDeclarations) {
statements = [...lexicalEnvironmentFunctionDeclarations];
}
if (lexicalEnvironmentVariableDeclarations) {
const statement = factory.createVariableStatement(
/*modifiers*/ undefined,
factory.createVariableDeclarationList(lexicalEnvironmentVariableDeclarations)
);
setEmitFlags(statement, EmitFlags.CustomPrologue);
if (!statements) {
statements = [statement];
}
else {
statements.push(statement);
}
}
if (lexicalEnvironmentStatements) {
if (!statements) {
statements = [...lexicalEnvironmentStatements];
}
else {
statements = [...statements, ...lexicalEnvironmentStatements];
}
}
}
// Restore the previous lexical environment.
lexicalEnvironmentStackOffset--;
lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset];
lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset];
lexicalEnvironmentStatements = lexicalEnvironmentStatementsStack[lexicalEnvironmentStackOffset];
lexicalEnvironmentFlags = lexicalEnvironmentFlagsStack[lexicalEnvironmentStackOffset];
if (lexicalEnvironmentStackOffset === 0) {
lexicalEnvironmentVariableDeclarationsStack = [];
lexicalEnvironmentFunctionDeclarationsStack = [];
lexicalEnvironmentStatementsStack = [];
lexicalEnvironmentFlagsStack = [];
}
return statements;
}
function setLexicalEnvironmentFlags(flags: LexicalEnvironmentFlags, value: boolean): void {
lexicalEnvironmentFlags = value ?
lexicalEnvironmentFlags | flags :
lexicalEnvironmentFlags & ~flags;
}
function getLexicalEnvironmentFlags(): LexicalEnvironmentFlags {
return lexicalEnvironmentFlags;
}
/**
* Starts a block scope. Any existing block hoisted variables are pushed onto the stack and the related storage variables are reset.
*/
function startBlockScope() {
Debug.assert(state > TransformationState.Uninitialized, "Cannot start a block scope during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot start a block scope after transformation has completed.");
blockScopedVariableDeclarationsStack[blockScopeStackOffset] = blockScopedVariableDeclarations;
blockScopeStackOffset++;
blockScopedVariableDeclarations = undefined!;
}
/**
* Ends a block scope. The previous set of block hoisted variables are restored. Any hoisted declarations are returned.
*/
function endBlockScope() {
Debug.assert(state > TransformationState.Uninitialized, "Cannot end a block scope during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot end a block scope after transformation has completed.");
const statements: Statement[] | undefined = some(blockScopedVariableDeclarations) ?
[
factory.createVariableStatement(
/*modifiers*/ undefined,
factory.createVariableDeclarationList(
blockScopedVariableDeclarations.map(identifier => factory.createVariableDeclaration(identifier)),
NodeFlags.Let
)
)
] : undefined;
blockScopeStackOffset--;
blockScopedVariableDeclarations = blockScopedVariableDeclarationsStack[blockScopeStackOffset];
if (blockScopeStackOffset === 0) {
blockScopedVariableDeclarationsStack = [];
}
return statements;
}
function addBlockScopedVariable(name: Identifier): void {
Debug.assert(blockScopeStackOffset > 0, "Cannot add a block scoped variable outside of an iteration body.");
(blockScopedVariableDeclarations || (blockScopedVariableDeclarations = [])).push(name);
}
function requestEmitHelper(helper: EmitHelper): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
Debug.assert(!helper.scoped, "Cannot request a scoped emit helper.");
if (helper.dependencies) {
for (const h of helper.dependencies) {
requestEmitHelper(h);
}
}
emitHelpers = append(emitHelpers, helper);
}
function readEmitHelpers(): EmitHelper[] | undefined {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
const helpers = emitHelpers;
emitHelpers = undefined;
return helpers;
}
function dispose() {
if (state < TransformationState.Disposed) {
// Clean up emit nodes on parse tree
for (const node of nodes) {
disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node)));
}
// Release references to external entries for GC purposes.
lexicalEnvironmentVariableDeclarations = undefined!;
lexicalEnvironmentVariableDeclarationsStack = undefined!;
lexicalEnvironmentFunctionDeclarations = undefined!;
lexicalEnvironmentFunctionDeclarationsStack = undefined!;
onSubstituteNode = undefined!;
onEmitNode = undefined!;
emitHelpers = undefined;
// Prevent further use of the transformation result.
state = TransformationState.Disposed;
}
}
}
const enum TransformationState {
Uninitialized,
Initialized,
Completed,
Disposed
}
const enum SyntaxKindFeatureFlags {
Substitution = 1 << 0,
EmitNotifications = 1 << 1,
}
/** @internal */
export const enum LexicalEnvironmentFlags {
None = 0,
InParameters = 1 << 0, // currently visiting a parameter list
VariablesHoistedInParameters = 1 << 1 // a temp variable was hoisted while visiting a parameter list
}
/** @internal */
export function noEmitSubstitution(_hint: EmitHint, node: Node) {
return node;
}
/** @internal */
export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) {
callback(hint, node);
}

View File

@@ -0,0 +1,502 @@
import { Symbol, ClassDeclaration, CompilerOptions, DeclarationName, DiagnosticWithLocation, EnumDeclaration, FunctionDeclaration, InterfaceDeclaration, ModuleDeclaration, ModuleKind, Node, PackageJsonInfoCache, Path, QualifiedName, SourceFile, SymbolFlags, TransformationContext as _TransformationContext, TypeAliasDeclaration, VariableStatement, NodeBuilderFlags, Statement, AccessorDeclaration, BindingElement, Declaration, ElementAccessExpression, EntityName, EntityNameOrEntityNameExpression, EnumMember, ExportDeclaration, Expression, Identifier, ImportCall, ImportDeclaration, ImportEqualsDeclaration, ImportTypeNode, ParameterDeclaration, PropertyAccessExpression, PropertyDeclaration, PropertySignature, SignatureDeclaration, StringLiteralLike, TypeNode, VariableDeclaration, VariableLikeDeclaration, ModuleBlock, LiteralTypeNode, BinaryExpression, ComputedPropertyName, NamedDeclaration, StringLiteral, ParenthesizedExpression, AsExpression, NonNullExpression, PartiallyEmittedExpression, SatisfiesExpression, TypeAssertion, EntityNameExpression, HasModifiers, Modifier, ModifierFlags, Program, UnparsedSource, FileReference, EmitFlags, EmitHelper, SourceMapRange, SynthesizedComment, TextRange, NoSubstitutionTemplateLiteral, MapLike } from "typescript";
import { AllAccessorDeclarations, AnyImportSyntax, DiagnosticMessage } from "./utils";
/** @internal */
export interface ResolveModuleNameResolutionHost {
getCanonicalFileName(p: string): string;
getCommonSourceDirectory(): string;
getCurrentDirectory(): string;
}
export interface TransformationContext extends _TransformationContext {
addDiagnostic(diag: DiagnosticWithLocation): void;
/** @internal */ getEmitResolver(): EmitResolver;
/** @internal */ getEmitHost(): EmitHost;
/** @internal */ getEmitHelperFactory(): EmitHelperFactory;
factory: _TransformationContext['factory'] & {
updateModifiers<T extends HasModifiers>(node: T, modifiers: readonly Modifier[] | ModifierFlags | undefined): T;
}
}
export interface EmitHost extends ModuleSpecifierResolutionHost, ResolveModuleNameResolutionHost {
getCommonSourceDirectory(): string
getCompilerOptions(): CompilerOptions
getSourceFiles(): SourceFile[]
/** @internal */ getSourceFileFromReference(referencingFile: SourceFile | UnparsedSource, ref: FileReference): SourceFile | undefined;
/** @internal */ getLibFileFromReference(ref: FileReference): SourceFile | undefined;
}
export interface EmitResolver {
hasGlobalName(name: string): boolean;
getReferencedExportContainer(node: Identifier, prefixLocals?: boolean): SourceFile | ModuleDeclaration | EnumDeclaration | undefined;
getReferencedImportDeclaration(node: Identifier): Declaration | undefined;
getReferencedDeclarationWithCollidingName(node: Identifier): Declaration | undefined;
isDeclarationWithCollidingName(node: Declaration): boolean;
isValueAliasDeclaration(node: Node): boolean;
isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean;
isTopLevelValueImportEqualsWithEntityName(node: ImportEqualsDeclaration): boolean;
// getNodeCheckFlags(node: Node): NodeCheckFlags;
isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean;
isLateBound(node: Declaration): node is LateBoundDeclaration;
collectLinkedAliases(node: Identifier, setVisibility?: boolean): Node[] | undefined;
isImplementationOfOverload(node: SignatureDeclaration): boolean | undefined;
isRequiredInitializedParameter(node: ParameterDeclaration): boolean;
isOptionalUninitializedParameterProperty(node: ParameterDeclaration): boolean;
isExpandoFunctionDeclaration(node: FunctionDeclaration): boolean;
getPropertiesOfContainerFunction(node: Declaration): Symbol[];
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
createReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
createTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration, tracker: SymbolTracker): Expression;
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags | undefined, shouldComputeAliasToMarkVisible: boolean): SymbolAccessibilityResult;
isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult;
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
getReferencedValueDeclaration(reference: Identifier): Declaration | undefined;
// getTypeReferenceSerializationKind(typeName: EntityName, location?: Node): TypeReferenceSerializationKind;
isOptionalParameter(node: ParameterDeclaration): boolean;
moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean;
isArgumentsLocalBinding(node: Identifier): boolean;
getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration | ImportTypeNode | ImportCall): SourceFile | undefined;
getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): [specifier: string, mode: ResolutionMode | undefined][] | undefined;
getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): [specifier: string, mode: ResolutionMode | undefined][] | undefined;
isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean;
getJsxFactoryEntity(location?: Node): EntityName | undefined;
getJsxFragmentFactoryEntity(location?: Node): EntityName | undefined;
getAllAccessorDeclarations(declaration: AccessorDeclaration): AllAccessorDeclarations;
getSymbolOfExternalModuleSpecifier(node: StringLiteralLike): Symbol | undefined;
isBindingCapturedByNode(node: Node, decl: VariableDeclaration | BindingElement): boolean;
getDeclarationStatementsForSourceFile(node: SourceFile, flags: NodeBuilderFlags, tracker: SymbolTracker, bundled?: boolean): Statement[] | undefined;
isImportRequiredByAugmentation(decl: ImportDeclaration): boolean;
}
/** @internal */
export interface AmbientModuleDeclaration extends ModuleDeclaration {
readonly body?: ModuleBlock;
}
export interface EmitHelperFactory {
}
export type GetSymbolAccessibilityDiagnostic = (symbolAccessibilityResult: SymbolAccessibilityResult) => (SymbolAccessibilityDiagnostic | undefined);
/** @internal */
export interface SymbolAccessibilityDiagnostic {
errorNode: Node;
diagnosticMessage: DiagnosticMessage;
typeName?: DeclarationName | QualifiedName;
}
export interface SymbolTracker {
// Called when the symbol writer encounters a symbol to write. Currently only used by the
// declaration emitter to help determine if it should patch up the final declaration file
// with import statements it previously saw (but chose not to emit).
trackSymbol?(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags): boolean;
reportInaccessibleThisError?(): void;
reportPrivateInBaseOfClassExpression?(propertyName: string): void;
reportInaccessibleUniqueSymbolError?(): void;
reportCyclicStructureError?(): void;
reportLikelyUnsafeImportRequiredError?(specifier: string): void;
reportTruncationError?(): void;
moduleResolverHost?: ModuleSpecifierResolutionHost & { getCommonSourceDirectory(): string };
trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void;
trackExternalModuleSymbolOfImportTypeNode?(symbol: Symbol): void;
reportNonlocalAugmentation?(containingFile: SourceFile, parentSymbol: Symbol, augmentingSymbol: Symbol): void;
reportNonSerializableProperty?(propertyName: string): void;
reportImportTypeNodeResolutionModeOverride?(): void;
}
/** @internal */
export interface ModuleSpecifierResolutionHost {
useCaseSensitiveFileNames?(): boolean;
fileExists(path: string): boolean;
getCurrentDirectory(): string;
directoryExists?(path: string): boolean;
readFile?(path: string): string | undefined;
realpath?(path: string): string;
getPackageJsonInfoCache?(): PackageJsonInfoCache & {
/** @internal */ getPackageJsonInfo(packageJsonPath: string): PackageJsonInfo | boolean | undefined;
} | undefined;
getGlobalTypingsCacheLocation?(): string | undefined;
getNearestAncestorDirectoryWithPackageJson?(fileName: string, rootDir?: string): string | undefined;
getProjectReferenceRedirect(fileName: string): string | undefined;
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
getSymlinkCache?(): {
getSymlinkedDirectoriesByRealpath(): MultiMap<Path, string> | undefined;
};
readonly redirectTargetsMap: RedirectTargetsMap;
}
export interface PackageJsonInfo {
packageDirectory: string;
contents: PackageJsonInfoContents;
}
/** @internal */
export interface PackageJsonInfoContents {
packageJsonContent: PackageJsonPathFields;
versionPaths: VersionPaths | undefined;
/** false: resolved to nothing. undefined: not yet resolved */
resolvedEntrypoints: string[] | false | undefined;
}
/** @internal */
export interface VersionPaths {
version: string;
paths: MapLike<string[]>;
}
export interface PackageJsonPathFields {
typings?: string;
types?: string;
typesVersions?: MapLike<MapLike<string[]>>;
main?: string;
tsconfig?: string;
type?: string;
imports?: object;
exports?: object;
name?: string;
}
export type RedirectTargetsMap = ReadonlyMap<Path, readonly string[]>;
export interface MultiMap<K, V> extends Map<K, V[]> {
/**
* Adds the value to an array of values associated with the key, and returns the array.
* Creates the array if it does not already exist.
*/
add(key: K, value: V): V[];
/**
* Removes a value from an array of values associated with the key.
* Does not preserve the order of those values.
* Does nothing if `key` is not in `map`, or `value` is not in `map[key]`.
*/
remove(key: K, value: V): void;
}
/** @internal */
export interface SymbolAccessibilityResult extends SymbolVisibilityResult {
errorModuleName?: string; // If the symbol is not visible from module, module's name
}
/** @internal */
export interface SymbolVisibilityResult {
accessibility: SymbolAccessibility;
aliasesToMakeVisible?: LateVisibilityPaintedStatement[]; // aliases that need to have this symbol visible
errorSymbolName?: string; // Optional symbol name that results in error
errorNode?: Node; // optional node that results in error
}
/** @internal */
export const enum SymbolAccessibility {
Accessible,
NotAccessible,
CannotBeNamed
}
/** @internal */
export type LateVisibilityPaintedStatement =
| AnyImportSyntax
| VariableStatement
| ClassDeclaration
| FunctionDeclaration
| ModuleDeclaration
| TypeAliasDeclaration
| InterfaceDeclaration
| EnumDeclaration;
/** @internal */
export type NodeId = number;
export type ResolutionMode = ModuleKind.ESNext | ModuleKind.CommonJS | undefined;
/** @internal */
export type GetCanonicalFileName = (fileName: string) => string;
/** @internal */
export const enum CharacterCodes {
nullCharacter = 0,
maxAsciiCharacter = 0x7F,
lineFeed = 0x0A, // \n
carriageReturn = 0x0D, // \r
lineSeparator = 0x2028,
paragraphSeparator = 0x2029,
nextLine = 0x0085,
// Unicode 3.0 space characters
space = 0x0020, // " "
nonBreakingSpace = 0x00A0, //
enQuad = 0x2000,
emQuad = 0x2001,
enSpace = 0x2002,
emSpace = 0x2003,
threePerEmSpace = 0x2004,
fourPerEmSpace = 0x2005,
sixPerEmSpace = 0x2006,
figureSpace = 0x2007,
punctuationSpace = 0x2008,
thinSpace = 0x2009,
hairSpace = 0x200A,
zeroWidthSpace = 0x200B,
narrowNoBreakSpace = 0x202F,
ideographicSpace = 0x3000,
mathematicalSpace = 0x205F,
ogham = 0x1680,
_ = 0x5F,
$ = 0x24,
_0 = 0x30,
_1 = 0x31,
_2 = 0x32,
_3 = 0x33,
_4 = 0x34,
_5 = 0x35,
_6 = 0x36,
_7 = 0x37,
_8 = 0x38,
_9 = 0x39,
a = 0x61,
b = 0x62,
c = 0x63,
d = 0x64,
e = 0x65,
f = 0x66,
g = 0x67,
h = 0x68,
i = 0x69,
j = 0x6A,
k = 0x6B,
l = 0x6C,
m = 0x6D,
n = 0x6E,
o = 0x6F,
p = 0x70,
q = 0x71,
r = 0x72,
s = 0x73,
t = 0x74,
u = 0x75,
v = 0x76,
w = 0x77,
x = 0x78,
y = 0x79,
z = 0x7A,
A = 0x41,
B = 0x42,
C = 0x43,
D = 0x44,
E = 0x45,
F = 0x46,
G = 0x47,
H = 0x48,
I = 0x49,
J = 0x4A,
K = 0x4B,
L = 0x4C,
M = 0x4D,
N = 0x4E,
O = 0x4F,
P = 0x50,
Q = 0x51,
R = 0x52,
S = 0x53,
T = 0x54,
U = 0x55,
V = 0x56,
W = 0x57,
X = 0x58,
Y = 0x59,
Z = 0x5a,
ampersand = 0x26, // &
asterisk = 0x2A, // *
at = 0x40, // @
backslash = 0x5C, // \
backtick = 0x60, // `
bar = 0x7C, // |
caret = 0x5E, // ^
closeBrace = 0x7D, // }
closeBracket = 0x5D, // ]
closeParen = 0x29, // )
colon = 0x3A, // :
comma = 0x2C, // ,
dot = 0x2E, // .
doubleQuote = 0x22, // "
equals = 0x3D, // =
exclamation = 0x21, // !
greaterThan = 0x3E, // >
hash = 0x23, // #
lessThan = 0x3C, // <
minus = 0x2D, // -
openBrace = 0x7B, // {
openBracket = 0x5B, // [
openParen = 0x28, // (
percent = 0x25, // %
plus = 0x2B, // +
question = 0x3F, // ?
semicolon = 0x3B, // ;
singleQuote = 0x27, // '
slash = 0x2F, // /
tilde = 0x7E, // ~
backspace = 0x08, // \b
formFeed = 0x0C, // \f
byteOrderMark = 0xFEFF,
tab = 0x09, // \t
verticalTab = 0x0B, // \v
}
/** @internal */
export type LiteralImportTypeNode = ImportTypeNode & { readonly argument: LiteralTypeNode & { readonly literal: StringLiteral } };
/** @internal */
export interface DynamicNamedDeclaration extends NamedDeclaration {
readonly name: ComputedPropertyName;
}
/** @internal */
export interface DynamicNamedBinaryExpression extends BinaryExpression {
readonly left: ElementAccessExpression;
}
/** @internal */
export interface JSDocTypeAssertion extends ParenthesizedExpression {
readonly _jsDocTypeAssertionBrand: never;
}
export type OuterExpression = ParenthesizedExpression | TypeAssertion | SatisfiesExpression | AsExpression | NonNullExpression | PartiallyEmittedExpression;
/** @internal */
export type AnyImportOrReExport = AnyImportSyntax | ExportDeclaration;
/** @internal */
// A declaration that supports late-binding (used in checker)
export interface LateBoundDeclaration extends DynamicNamedDeclaration {
readonly name: LateBoundName;
}
/** @internal */
// A name that supports late-binding (used in checker)
export interface LateBoundName extends ComputedPropertyName {
readonly expression: EntityNameExpression;
}
/** @internal */
export interface EmitFileNames {
jsFilePath?: string | undefined;
sourceMapFilePath?: string | undefined;
declarationFilePath?: string | undefined;
declarationMapPath?: string | undefined;
buildInfoPath?: string | undefined;
}
export type _FileReference = FileReference
/** @internal */
export type ExportedModulesFromDeclarationEmit = readonly Symbol[];
export type _StringLiteralLike = StringLiteralLike
export type _Symbol = Symbol
export type _Path = Path
export type _ModifierFlags = ModifierFlags
declare module 'typescript' {
interface Node {
symbol: _Symbol;
emitNode?: EmitNode;
original: this;
modifierFlagsCache: _ModifierFlags
}
interface Symbol {
isReferenced: boolean;
parent: _Symbol;
}
interface Bundle {
/** @internal */ syntheticFileReferences?: readonly _FileReference[];
/** @internal */ syntheticTypeReferences?: readonly _FileReference[];
/** @internal */ syntheticLibReferences?: readonly _FileReference[];
/** @internal */ hasNoDefaultLib?: boolean;
}
interface SourceFile {
exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit;
imports: readonly _StringLiteralLike[];
path: _Path;
}
interface CompilerOptions {
/**
* The directory of the config file that specified 'paths'. Used to resolve relative paths when 'baseUrl' is absent.
*
* @internal
*/
pathsBasePath?: string;
configFilePath?: _Path;
}
}
/** @internal */
export interface EmitNode {
annotatedNodes?: Node[]; // Tracks Parse-tree nodes with EmitNodes for eventual cleanup.
flags: EmitFlags; // Flags that customize emit
leadingComments?: SynthesizedComment[]; // Synthesized leading comments
trailingComments?: SynthesizedComment[]; // Synthesized trailing comments
commentRange?: TextRange; // The text range to use when emitting leading or trailing comments
sourceMapRange?: SourceMapRange; // The text range to use when emitting leading or trailing source mappings
tokenSourceMapRanges?: (SourceMapRange | undefined)[]; // The text range to use when emitting source mappings for tokens
constantValue?: string | number; // The constant value of an expression
externalHelpersModuleName?: Identifier; // The local name for an imported helpers module
externalHelpers?: boolean;
helpers?: EmitHelper[]; // Emit helpers for the node
startsOnNewLine?: boolean; // If the node should begin on a new line
snippetElement?: SnippetElement; // Snippet element of the node
typeNode?: TypeNode; // VariableDeclaration type
}
/** @internal */
export type SnippetElement = TabStop | Placeholder;
/** @internal */
export interface TabStop {
kind: SnippetKind.TabStop;
order: number;
}
/** @internal */
export interface Placeholder {
kind: SnippetKind.Placeholder;
order: number;
}
// Reference: https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax
/** @internal */
export const enum SnippetKind {
TabStop, // `$1`, `$2`
Placeholder, // `${1:foo}`
Choice, // `${1|one,two,three|}`
Variable, // `$name`, `${name:default}`
}
/** @internal */
export interface ModulePath {
path: string;
isInNodeModules: boolean;
isRedirect: boolean;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,113 @@
import * as ts from 'typescript'
import * as fsp from 'fs/promises'
import * as fs from 'fs'
import * as path from 'path'
import { transformFile } from './compiler/transform-file';
import { ArgType, parseArgs } from './utils/cli-parser';
import { ensureDir, readAllFiles } from './utils/fs-utils';
import { changeAnyExtension, normalizePath } from './compiler/path-utils';
(ts as any).Debug.enableDebugInfo();
const { value: parsedArgs, printUsageOnErrors } = parseArgs(process.argv.slice(2), {
default: ArgType.StringArray(),
project: {
type: ArgType.String(),
description: "Project configuration",
required: true
},
watch: {
type: ArgType.Boolean(),
description: "Keep watching",
},
declarationDir: {
type: ArgType.String(),
description: "Keep watching",
}
});
printUsageOnErrors();
let projectConfig = normalizePath(path.resolve(parsedArgs.project));
let projectPath = projectConfig;
if (path.extname(projectConfig) !== ".json") {
projectConfig = normalizePath(path.join(projectConfig, "tsconfig.json"))
} else {
projectPath = normalizePath(path.dirname(projectConfig));
}
let watched: Array<{
watcher: fs.FSWatcher,
path: string,
}> = [];
function watch(rootDir: string) {
if (parsedArgs.watch) {
let newWatched: Array<string>;
if (parsedArgs.default) {
newWatched = parsedArgs.default;
} else {
newWatched = [rootDir];
}
if(watched.length != newWatched.length || !watched.every((v, index) => v.path === newWatched[index])) {
watched.forEach(f => f.watcher.close());
watched = newWatched.map(f => ({
path: f,
watcher: fs.watch(f, { persistent: true, recursive: true }, cancelAndRestart),
}))
}
}
}
let lastRunCancellation:CancellationToken = { isCancelled: false }
async function delay(ms:number) {
return new Promise(r => setTimeout(r, ms));
}
function cancelAndRestart(event: fs.WatchEventType, filename: string) {
console.log(event, filename)
lastRunCancellation.isCancelled = true;
lastRunCancellation = { isCancelled: false };
main(lastRunCancellation, 50);
}
async function main(cancellationToken: CancellationToken, msDelay: number) {
await delay(msDelay);
if(cancellationToken.isCancelled) return;
console.log("Detected changes rebuilding")
const tsconfig = ts.readConfigFile(projectConfig, ts.sys.readFile);
const parsed = ts.parseJsonConfigFileContent(tsconfig.config, ts.sys, "./");
const options = parsed.options;
const rootDir = options.rootDir ? normalizePath(path.resolve(path.join(projectPath, options.rootDir))) : projectPath;
const files = parsedArgs.default ?? readAllFiles(rootDir, /.*\.ts/).filter(t => !/[\\/]node_modules[\\/]/.exec(t));
watch(rootDir);
if(cancellationToken.isCancelled) return;
await transformProjectFiles(rootDir, files, options, cancellationToken);
}
main(lastRunCancellation, 0);
type CancellationToken = { isCancelled: boolean };
async function transformProjectFiles(rootDir: string, files: string[], options: ts.CompilerOptions, cancellationToken: CancellationToken) {
const declarationDir = parsedArgs.declarationDir ? normalizePath(path.resolve(parsedArgs.declarationDir)) :
options.outDir ? normalizePath(path.resolve(options.outDir)) :
undefined;
for (let file of files) {
try {
const source = await fsp.readFile(file, { encoding: "utf8" });
if(cancellationToken.isCancelled) return;
const actualDeclaration = transformFile(file, source, [], [], options, ts.ModuleKind.ESNext);
const output =
declarationDir? changeAnyExtension(file.replace(rootDir, declarationDir), ".d.ts"):
changeAnyExtension(file, ".d.ts");
await ensureDir(path.dirname(output));
await fsp.writeFile(output, actualDeclaration.code);
} catch (e) {
console.error(`Failed to transform: ${file}`, e)
}
}
return { rootDir };
}

View File

@@ -0,0 +1,17 @@
import { parserConfiguration, ArgType } from "../utils/cli-parser";
export const testRunnerCLIConfiguration = parserConfiguration({
default: {
type: ArgType.String(),
description: "Test filter to run",
},
type: {
type: ArgType.Enum("all", "tsc", "i"),
description: "Type of run (Typescript, all, or isolated)",
},
shard: ArgType.Number(),
shardCount: ArgType.Number(),
histFolder: ArgType.String(),
libPath: ArgType.String(),
rootPaths: ArgType.StringArray(),
})

View File

@@ -0,0 +1,21 @@
export const excludedTsTests = new Set([
// These two tests contain a declaration error
// A variable that is not of a literal type is used as a computed property name
// TS will error on this and will not merge the overloads to a singe symbol
// This means TS will not remove the implementation overload
//
// The external declaration emitter will use the variable name to merge symbols.
"computedPropertyNamesOnOverloads_ES5",
"computedPropertyNamesOnOverloads_ES6",
// Symbols are merged across files and excluded. We can't do cross file analysis
"jsFileCompilationDuplicateFunctionImplementation",
"jsFileCompilationDuplicateFunctionImplementationFileOrderReversed",
// Output emitted to same file test ios not of interest for isolated declarations
"filesEmittingIntoSameOutput",
// Error in TS, but excludes implementation in declarations.
// Generating same output if we have an error is best effort
"overloadsInDifferentContainersDisagreeOnAmbient",
// The function is elided because the eval function is merged with eval from libs into a single symbol
// We can't reproduce this behavior
"parserStrictMode8",
]);

View File

@@ -0,0 +1,82 @@
import * as fs from 'fs'
import * as path from 'path'
import * as childProcess from "child_process";
import { parseArgs } from '../utils/cli-parser';
import { testRunnerCLIConfiguration } from './cli-arg-config';
type ExecuteResult = {
error: childProcess.ExecException | null
stdout: string,
stderr: string,
}
function exec(cmd: string, dir: string, onStdOut: (s: string) => void) {
return new Promise<ExecuteResult>((resolve) => {
console.log(`In ${dir} Executing: ${cmd}`);
const ls = childProcess.spawn(cmd, [], {
cwd: path.resolve(path.join(process.cwd(), dir)),
shell: true
});
let stdout = ""
let stderr = ""
ls.stdout.on('data', function (data) {
if(!onStdOut) {
process.stdout.write(data.toString());
} else {
onStdOut(data.toString());
}
stdout += data.toString();
});
ls.stderr.on('data', function (data) {
process.stderr.write(data.toString());
stderr += data.toString();
});
ls.on('error', function(err) {
console.log(err);
})
ls.on('exit', function (code) {
console.log('exited:' + code?.toString());
resolve({
error: !code ? null : Object.assign(new Error(""), {
code,
cmd: cmd,
}),
stderr,
stdout
})
});
})
}
const { value: parsedArgs, printUsageOnErrors } = parseArgs(process.argv.slice(2), testRunnerCLIConfiguration);
printUsageOnErrors();
const shardCount = parsedArgs.shardCount ?? 6;
async function main() {
let runCount = 0;
const date = new Date();
const histFolder = `${date.toISOString().replace(/:/g, "-")}-${parsedArgs.type}`;
const commandLine = `node ./build/test-runner/test-runner-main.js --histFolder=${histFolder} ${process.argv.slice(2).join(" ")} `;
let lastWrite = new Date().getTime();
const startTime = new Date().getTime();
const elapsedTime = (now: number) => `${((now - startTime) / 1000 / 60).toFixed(2)} minutes`
const promisees = Array.from({ length: shardCount}).map(async (_, index) => {
await exec(commandLine + ` --shard=${index}`, "./", out => {
runCount += (out.match(/Ran:/g) || []).length;
if(new Date().getTime() - lastWrite > 2000) {
lastWrite = new Date().getTime()
console.log(`Run count: ${runCount} after ${elapsedTime(lastWrite)}`);
}
});
console.log(`Shard ${index} completed`);
});
await Promise.all(promisees);
const endTime = new Date().getTime();
console.log(`Took ${elapsedTime(endTime)} to complete ${runCount}`)
}
main();

View File

@@ -0,0 +1,138 @@
import 'source-map-support/register';
import * as path from 'path'
import * as fs from 'fs/promises'
import { loadTestCase, runTypeScript, runIsolated, FileContent } from "./utils";
import { IO } from './tsc-infrastructure/io';
import { changeExtension } from './tsc-infrastructure/vpath';
import { CompilerSettings, TestCaseContent } from './tsc-infrastructure/test-file-parser';
import * as ts from 'typescript'
import { normalizePath, removeExtension } from '../compiler/path-utils';
import { excludedTsTests } from './excluded-ts-tests';
import { getFileBasedTestConfigurationDescription, getFileBasedTestConfigurations } from './tsc-infrastructure/vary-by';
import { setCompilerOptionsFromHarnessSetting } from './tsc-infrastructure/compiler-run';
import { parseArgs } from '../utils/cli-parser';
import { testRunnerCLIConfiguration } from './cli-arg-config';
import { addToQueue, ensureDir, flushQueue, readAllFiles } from '../utils/fs-utils';
const excludeFilter =/\/fourslash\//;
const { value: parsedArgs, printUsageOnErrors } = parseArgs(process.argv.slice(2), testRunnerCLIConfiguration);
printUsageOnErrors();
const shard = parsedArgs.shard
const shardCount = parsedArgs.shardCount
let prefix: string | undefined = undefined;
const prefixed = parsedArgs.default && /(?<index>[0-9]{5})-(((?<name>.*)\.(?<options>(.*=.*)+)(\.d\.ts))|(?<nameSimple>.*))/.exec(parsedArgs.default);
let testVersionFilter: string | undefined = undefined;
if(prefixed) {
prefix = prefixed.groups?.index;
parsedArgs.default = prefixed.groups?.name ?? prefixed.groups?.nameSimple;
testVersionFilter = prefixed.groups?.options;
}
const rootCasePaths = parsedArgs.rootPaths ?? [ './tests/sources' ]
const libFolder = parsedArgs.libPath ?? path.join(rootCasePaths[0], "../lib")
const filter = parsedArgs.default ? new RegExp(parsedArgs.default) : /.*\.ts/
const runType =
parsedArgs.type === "all" ? { tsc: true, isolated: true } :
parsedArgs.type === "tsc" ? { tsc: true, isolated: false } :
{ tsc: false, isolated: true };
const allTests = rootCasePaths
.map(r => readAllFiles(r, filter))
.flat()
.filter(f => !excludeFilter.exec(f));
(ts as any).Debug.enableDebugInfo();
const date = new Date();
const historical = (parsedArgs.histFolder && `/${parsedArgs.histFolder}/`) ?? `/${date.toISOString().replace(/:/g, "-")}-${parsedArgs.type}/`;
function pad(num: number, size: number) { return ('000000000' + num).substr(-size); }
async function main() {
const libFiles = (await fs.readdir(libFolder)).map(n => normalizePath(path.join("/.lib", n)));
const testsPerShared = shardCount && Math.round(allTests.length / shardCount);
const [start, end] = shard == undefined || shardCount == undefined || testsPerShared == undefined ?
[0, allTests.length] :
[shard * testsPerShared, (shard == shardCount - 1) ? allTests.length : (shard + 1) * testsPerShared];
for (let count = start; count < end; count++) {
const testFile = normalizePath(allTests[count]);
const testFileNoExtension = removeExtension(path.basename(testFile), path.extname(testFile));
if(excludedTsTests.has(testFileNoExtension)) {
continue;
}
const data = await loadTestCase(testFile);
const variedConfiguration = getFileBasedTestConfigurations(data.settings) ?? [{}];
for(const varConfig of variedConfiguration) {
const varConfigDescription = getFileBasedTestConfigurationDescription(varConfig);
if (testVersionFilter && varConfigDescription !== testVersionFilter) continue;
const file = (prefix ?? pad(count, 5)) + "-" + changeExtension(path.basename(testFile), varConfigDescription + ".d.ts");
if (runType.tsc) runAndWrite(path.join("./tsc-tests/$now/tsc", file), varConfig, runTypeScript);
if (runType.isolated) runAndWrite(path.join("./tsc-tests/$now/isolated", file), varConfig, (t, s) => runIsolated(t, libFiles, s));
}
console.log(` Ran: ${pad(count, 5)}/${allTests.length}`)
function runAndWrite(file: string, varySettings: CompilerSettings, fn: (data: TestCaseContent, opts: ts.CompilerOptions) => FileContent[]) {
const settings: ts.CompilerOptions = {};
setCompilerOptionsFromHarnessSetting(data.settings, settings);
setCompilerOptionsFromHarnessSetting(varySettings, settings);
// Not supported
delete settings.outFile;
delete settings.out;
delete settings.outDir;
delete settings.declarationDir;
const results = safeRun(d => fn(d, settings));
const resultText = results
.flatMap(r => [
"// " + r.fileName,
r.content
])
.join(IO.newLine());
if (allTests.length > 5) {
writeResults(normalizePath(file).replace("/$now/", historical), resultText);
}
writeResults(file, resultText);
}
function safeRun(fn: (data: TestCaseContent) => FileContent[]): FileContent[] {
try {
return fn(data)
} catch (e) {
return [{
fileName: path.basename(testFile),
content: `
==== ERROR ====
message: ${e.message},
${e.stack},
`,
}]
}
}
async function writeResults(fileName: string, results: string) {
addToQueue(async () => {
const dirName = path.dirname(fileName);
await ensureDir(dirName);
await fs.writeFile(fileName, results);
console.log(`Written: ${pad(count, 5)}/${allTests.length}`)
});
}
}
await flushQueue();
}
main();

View File

@@ -0,0 +1,326 @@
import * as ts from '../../compiler/lang-utils'
export interface SortOptions<T> {
comparer: (a: T, b: T) => number;
sort: "insertion" | "comparison";
}
export class SortedMap<K, V> {
private _comparer: (a: K, b: K) => number;
private _keys: K[] = [];
private _values: V[] = [];
private _order: number[] | undefined;
private _version = 0;
private _copyOnWrite = false;
constructor(comparer: ((a: K, b: K) => number) | SortOptions<K>, iterable?: Iterable<[K, V]>) {
this._comparer = typeof comparer === "object" ? comparer.comparer : comparer;
this._order = typeof comparer === "object" && comparer.sort === "insertion" ? [] : undefined;
if (iterable) {
const iterator = getIterator(iterable);
try {
for (let i = nextResult(iterator); i; i = nextResult(iterator)) {
const [key, value] = i.value;
this.set(key, value);
}
}
finally {
closeIterator(iterator);
}
}
}
public get size() {
return this._keys.length;
}
public get comparer() {
return this._comparer;
}
public get [Symbol.toStringTag]() {
return "SortedMap";
}
public has(key: K) {
return ts.binarySearch(this._keys, key, ts.identity, this._comparer) >= 0;
}
public get(key: K) {
const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
return index >= 0 ? this._values[index] : undefined;
}
public getEntry(key: K): [ K, V ] | undefined {
const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
return index >= 0 ? [ this._keys[index], this._values[index] ] : undefined;
}
public set(key: K, value: V) {
const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
if (index >= 0) {
this._values[index] = value;
}
else {
this.writePreamble();
insertAt(this._keys, ~index, key);
insertAt(this._values, ~index, value);
if (this._order) insertAt(this._order, ~index, this._version);
this.writePostScript();
}
return this;
}
public delete(key: K) {
const index = ts.binarySearch(this._keys, key, ts.identity, this._comparer);
if (index >= 0) {
this.writePreamble();
ts.orderedRemoveItemAt(this._keys, index);
ts.orderedRemoveItemAt(this._values, index);
if (this._order) ts.orderedRemoveItemAt(this._order, index);
this.writePostScript();
return true;
}
return false;
}
public clear() {
if (this.size > 0) {
this.writePreamble();
this._keys.length = 0;
this._values.length = 0;
if (this._order) this._order.length = 0;
this.writePostScript();
}
}
public forEach(callback: (value: V, key: K, collection: this) => void, thisArg?: any) {
const keys = this._keys;
const values = this._values;
const indices = this.getIterationOrder();
const version = this._version;
this._copyOnWrite = true;
try {
if (indices) {
for (const i of indices) {
callback.call(thisArg, values[i], keys[i], this);
}
}
else {
for (let i = 0; i < keys.length; i++) {
callback.call(thisArg, values[i], keys[i], this);
}
}
}
finally {
if (version === this._version) {
this._copyOnWrite = false;
}
}
}
public * keys() {
const keys = this._keys;
const indices = this.getIterationOrder();
const version = this._version;
this._copyOnWrite = true;
try {
if (indices) {
for (const i of indices) {
yield keys[i];
}
}
else {
yield* keys;
}
}
finally {
if (version === this._version) {
this._copyOnWrite = false;
}
}
}
public * values() {
const values = this._values;
const indices = this.getIterationOrder();
const version = this._version;
this._copyOnWrite = true;
try {
if (indices) {
for (const i of indices) {
yield values[i];
}
}
else {
yield* values;
}
}
finally {
if (version === this._version) {
this._copyOnWrite = false;
}
}
}
public * entries() {
const keys = this._keys;
const values = this._values;
const indices = this.getIterationOrder();
const version = this._version;
this._copyOnWrite = true;
try {
if (indices) {
for (const i of indices) {
yield [keys[i], values[i]] as [K, V];
}
}
else {
for (let i = 0; i < keys.length; i++) {
yield [keys[i], values[i]] as [K, V];
}
}
}
finally {
if (version === this._version) {
this._copyOnWrite = false;
}
}
}
public [Symbol.iterator]() {
return this.entries();
}
private writePreamble() {
if (this._copyOnWrite) {
this._keys = this._keys.slice();
this._values = this._values.slice();
if (this._order) this._order = this._order.slice();
this._copyOnWrite = false;
}
}
private writePostScript() {
this._version++;
}
private getIterationOrder() {
if (this._order) {
const order = this._order;
return this._order
.map((_, i) => i)
.sort((x, y) => order[x] - order[y]);
}
return undefined;
}
}
export function insertAt<T>(array: T[], index: number, value: T): void {
if (index === 0) {
array.unshift(value);
}
else if (index === array.length) {
array.push(value);
}
else {
for (let i = array.length; i > index; i--) {
array[i] = array[i - 1];
}
array[index] = value;
}
}
export function getIterator<T>(iterable: Iterable<T>): Iterator<T> {
return iterable[Symbol.iterator]();
}
export function nextResult<T>(iterator: Iterator<T>): IteratorResult<T> | undefined {
const result = iterator.next();
return result.done ? undefined : result;
}
export function closeIterator<T>(iterator: Iterator<T>) {
const fn = iterator.return;
if (typeof fn === "function") fn.call(iterator);
}
/**
* A collection of metadata that supports inheritance.
*/
export class Metadata {
private static readonly _undefinedValue = {};
private _parent: Metadata | undefined;
private _map: { [key: string]: any };
private _version = 0;
private _size = -1;
private _parentVersion: number | undefined;
constructor(parent?: Metadata) {
this._parent = parent;
this._map = Object.create(parent ? parent._map : null); // eslint-disable-line no-null/no-null
}
public get size(): number {
if (this._size === -1 || (this._parent && this._parent._version !== this._parentVersion)) {
let size = 0;
for (const _ in this._map) size++;
this._size = size;
if (this._parent) {
this._parentVersion = this._parent._version;
}
}
return this._size;
}
public get parent() {
return this._parent;
}
public has(key: string): boolean {
return this._map[Metadata._escapeKey(key)] !== undefined;
}
public get(key: string): any {
const value = this._map[Metadata._escapeKey(key)];
return value === Metadata._undefinedValue ? undefined : value;
}
public set(key: string, value: any): this {
this._map[Metadata._escapeKey(key)] = value === undefined ? Metadata._undefinedValue : value;
this._size = -1;
this._version++;
return this;
}
public delete(key: string): boolean {
const escapedKey = Metadata._escapeKey(key);
if (this._map[escapedKey] !== undefined) {
delete this._map[escapedKey];
this._size = -1;
this._version++;
return true;
}
return false;
}
public clear(): void {
this._map = Object.create(this._parent ? this._parent._map : null); // eslint-disable-line no-null/no-null
this._size = -1;
this._version++;
}
public forEach(callback: (value: any, key: string, map: this) => void) {
for (const key in this._map) {
callback(this._map[key], Metadata._unescapeKey(key), this);
}
}
private static _escapeKey(text: string) {
return (text.length >= 2 && text.charAt(0) === "_" && text.charAt(1) === "_" ? "_" + text : text);
}
private static _unescapeKey(text: string) {
return (text.length >= 3 && text.charAt(0) === "_" && text.charAt(1) === "_" && text.charAt(2) === "_" ? text.slice(1) : text);
}
}

View File

@@ -0,0 +1,231 @@
import { CompilerOptions, Diagnostic } from "typescript";
import ts = require("typescript");
import { hasProperty } from "../../compiler/debug";
import { trimString, startsWith, mapDefined, map, compareStringsCaseSensitive } from "../../compiler/lang-utils";
import * as opts from "./options";
import * as TestCaseParser from "./test-file-parser";
import * as vfs from "./vfs";
import * as vpath from "./vpath";
import { parseCustomTypeOption, parseListTypeOption } from "./options";
import { fileExtensionIs, getNormalizedAbsolutePath, normalizeSlashes, toPath } from "../../compiler/path-utils";
import { cloneCompilerOptions, getEmitScriptTarget } from "../../compiler/utils";
import * as documents from './test-document';
import { createGetCanonicalFileName } from "../../compiler/path-utils";
import * as compiler from './compiler';
import * as fakes from './fakesHosts';
import { IO } from "./io";
interface HarnessOptions {
useCaseSensitiveFileNames?: boolean;
includeBuiltFile?: string;
baselineFile?: string;
libFiles?: string;
noTypesAndSymbols?: boolean;
}
// Additional options not already in ts.optionDeclarations
const harnessOptionDeclarations: opts.CommandLineOption[] = [
{ name: "allowNonTsExtensions", type: "boolean", defaultValueDescription: false },
{ name: "useCaseSensitiveFileNames", type: "boolean", defaultValueDescription: false },
{ name: "baselineFile", type: "string" },
{ name: "includeBuiltFile", type: "string" },
{ name: "fileName", type: "string" },
{ name: "libFiles", type: "string" },
{ name: "noErrorTruncation", type: "boolean", defaultValueDescription: false },
{ name: "suppressOutputPathCheck", type: "boolean", defaultValueDescription: false },
{ name: "noImplicitReferences", type: "boolean", defaultValueDescription: false },
{ name: "currentDirectory", type: "string" },
{ name: "symlink", type: "string" },
{ name: "link", type: "string" },
{ name: "noTypesAndSymbols", type: "boolean", defaultValueDescription: false },
// Emitted js baseline will print full paths for every output file
{ name: "fullEmitPaths", type: "boolean", defaultValueDescription: false },
];
let optionsIndex: Map<string, opts.CommandLineOption>;
function getCommandLineOption(name: string): opts.CommandLineOption | undefined {
if (!optionsIndex) {
optionsIndex = new Map<string, opts.CommandLineOption>();
const optionDeclarations = harnessOptionDeclarations.concat(opts.optionDeclarations);
for (const option of optionDeclarations) {
optionsIndex.set(option.name.toLowerCase(), option);
}
}
return optionsIndex.get(name.toLowerCase());
}
export function setCompilerOptionsFromHarnessSetting(settings: TestCaseParser.CompilerSettings, options: CompilerOptions & HarnessOptions): void {
for (const name in settings) {
if (hasProperty(settings, name)) {
const value = settings[name];
if (value === undefined) {
throw new Error(`Cannot have undefined value for compiler option '${name}'.`);
}
const option = getCommandLineOption(name);
if (option) {
const errors: Diagnostic[] = [];
options[option.name] = optionValue(option, value, errors);
if (errors.length > 0) {
throw new Error(`Unknown value '${value}' for compiler option '${name}'.`);
}
}
}
}
}
function optionValue(option: opts.CommandLineOption, value: string, errors: Diagnostic[]): any {
switch (option.type) {
case "boolean":
return value.toLowerCase() === "true";
case "string":
return value;
case "number": {
const numverValue = parseInt(value, 10);
if (isNaN(numverValue)) {
throw new Error(`Value must be a number, got: ${JSON.stringify(value)}`);
}
return numverValue;
}
// If not a primitive, the possible types are specified in what is effectively a map of options.
case "list":
return parseListTypeOption(option, value, errors);
default:
return parseCustomTypeOption(option as opts.CommandLineOptionOfCustomType, value, errors);
}
}
export interface TestFile {
unitName: string;
content: string;
fileOptions?: any;
}
export function compileFiles(
inputFiles: TestFile[],
otherFiles: TestFile[],
harnessSettings: TestCaseParser.CompilerSettings | undefined,
compilerOptions: ts.CompilerOptions | undefined,
// Current directory is needed for rwcRunner to be able to use currentDirectory defined in json file
currentDirectory: string | undefined,
symlinks?: vfs.FileSet
): compiler.CompilationResult {
const options: ts.CompilerOptions & HarnessOptions = compilerOptions ? cloneCompilerOptions(compilerOptions) : { noResolve: false };
options.target = getEmitScriptTarget(options);
options.newLine = options.newLine || ts.NewLineKind.CarriageReturnLineFeed;
options.noErrorTruncation = true;
options.skipDefaultLibCheck = typeof options.skipDefaultLibCheck === "undefined" ? true : options.skipDefaultLibCheck;
if (typeof currentDirectory === "undefined") {
currentDirectory = vfs.srcFolder;
}
// Parse settings
if (harnessSettings) {
setCompilerOptionsFromHarnessSetting(harnessSettings, options);
}
if (options.rootDirs) {
options.rootDirs = map(options.rootDirs, d => getNormalizedAbsolutePath(d, currentDirectory));
}
const useCaseSensitiveFileNames = options.useCaseSensitiveFileNames !== undefined ? options.useCaseSensitiveFileNames : true;
const programFileNames = inputFiles.map(file => file.unitName).filter(fileName => !fileExtensionIs(fileName, ts.Extension.Json));
// Files from built\local that are requested by test "@includeBuiltFiles" to be in the context.
// Treat them as library files, so include them in build, but not in baselines.
if (options.includeBuiltFile) {
programFileNames.push(vpath.combine(vfs.builtFolder, options.includeBuiltFile));
}
// Files from tests\lib that are requested by "@libFiles"
if (options.libFiles) {
for (const fileName of options.libFiles.split(",")) {
programFileNames.push(vpath.combine(vfs.testLibFolder, fileName));
}
}
const docs = inputFiles.concat(otherFiles).map(documents.TextDocument.fromTestFile);
const fs = vfs.createFromFileSystem(IO, !useCaseSensitiveFileNames, { documents: docs, cwd: currentDirectory });
if (symlinks) {
fs.apply(symlinks);
}
const host = new fakes.CompilerHost(fs, options);
const result = compiler.compileFiles(host, programFileNames, options);
result.symlinks = symlinks;
return result;
}
export function* iterateOutputs(outputFiles: Iterable<documents.TextDocument>): IterableIterator<[string, string]> {
// Collect, test, and sort the fileNames
const files = Array.from(outputFiles);
files.slice().sort((a, b) => compareStringsCaseSensitive(cleanName(a.file), cleanName(b.file)));
const dupeCase = new Map<string, number>();
// Yield them
for (const outputFile of files) {
yield [checkDuplicatedFileName(outputFile.file, dupeCase), "/*====== " + outputFile.file + " ======*/\r\n" + Utils.removeByteOrderMark(outputFile.text)];
}
function cleanName(fn: string) {
const lastSlash = normalizeSlashes(fn).lastIndexOf("/");
return fn.substr(lastSlash + 1).toLowerCase();
}
}
function checkDuplicatedFileName(resultName: string, dupeCase: Map<string, number>): string {
resultName = sanitizeTestFilePath(resultName);
if (dupeCase.has(resultName)) {
// A different baseline filename should be manufactured if the names differ only in case, for windows compat
const count = 1 + dupeCase.get(resultName)!;
dupeCase.set(resultName, count);
resultName = `${resultName}.dupe${count}`;
}
else {
dupeCase.set(resultName, 0);
}
return resultName;
}
export function sanitizeTestFilePath(name: string) {
const path = toPath(normalizeSlashes(name.replace(/[\^<>:"|?*%]/g, "_")).replace(/\.\.\//g, "__dotdot/"), "", Utils.canonicalizeForHarness);
if (startsWith(path, "/")) {
return path.substring(1);
}
return path;
}
export namespace Utils {
export const canonicalizeForHarness = createGetCanonicalFileName(/*caseSensitive*/ false); // This is done so tests work on windows _and_ linux
export function removeByteOrderMark(text: string): string {
const length = getByteOrderMarkLength(text);
return length ? text.slice(length) : text;
}
export function getByteOrderMarkLength(text: string): number {
if (text.length >= 1) {
const ch0 = text.charCodeAt(0);
if (ch0 === 0xfeff) return 1;
if (ch0 === 0xfe) return text.length >= 2 && text.charCodeAt(1) === 0xff ? 2 : 0;
if (ch0 === 0xff) return text.length >= 2 && text.charCodeAt(1) === 0xfe ? 2 : 0;
if (ch0 === 0xef) return text.length >= 3 && text.charCodeAt(1) === 0xbb && text.charCodeAt(2) === 0xbf ? 3 : 0;
}
return 0;
}
function checkDuplicatedFileName(resultName: string, dupeCase: Map<string, number>): string {
resultName = sanitizeTestFilePath(resultName);
if (dupeCase.has(resultName)) {
// A different baseline filename should be manufactured if the names differ only in case, for windows compat
const count = 1 + dupeCase.get(resultName)!;
dupeCase.set(resultName, count);
resultName = `${resultName}.dupe${count}`;
}
else {
dupeCase.set(resultName, 0);
}
return resultName;
}
export function addUTF8ByteOrderMark(text: string) {
return getByteOrderMarkLength(text) === 0 ? "\u00EF\u00BB\u00BF" + text : text;
}
}

View File

@@ -0,0 +1,270 @@
import * as ts from "typescript";
import * as lang from "../../compiler/lang-utils";
import * as vpath from "./vpath";
import * as documents from "./test-document";
import * as vfs from "./vfs";
import * as collections from "./collections";
import * as fakes from './fakesHosts';
import { getDeclarationEmitExtensionForPath, getOutputExtension } from "../../compiler/utils";
import { fileExtensionIs } from "../../compiler/path-utils";
/**
* Test harness compiler functionality.
*/
export interface Project {
file: string;
config?: ts.ParsedCommandLine;
errors?: ts.Diagnostic[];
}
export function readProject(host: fakes.ParseConfigHost, project: string | undefined, existingOptions?: ts.CompilerOptions): Project | undefined {
if (project) {
project = vpath.isTsConfigFile(project) ? project : vpath.combine(project, "tsconfig.json");
}
else {
[project] = host.vfs.scanSync(".", "ancestors-or-self", {
accept: (path, stats) => stats.isFile() && host.vfs.stringComparer(vpath.basename(path), "tsconfig.json") === 0
});
}
if (project) {
// TODO(rbuckton): Do we need to resolve this? Resolving breaks projects tests.
// project = vpath.resolve(host.vfs.currentDirectory, project);
// read the config file
const readResult = ts.readConfigFile(project, path => host.readFile(path));
if (readResult.error) {
return { file: project, errors: [readResult.error] };
}
// parse the config file
const config = ts.parseJsonConfigFileContent(readResult.config, host, vpath.dirname(project), existingOptions);
return { file: project, errors: config.errors, config };
}
}
/**
* Correlates compilation inputs and outputs
*/
export interface CompilationOutput {
readonly inputs: readonly documents.TextDocument[];
readonly js: documents.TextDocument | undefined;
readonly dts: documents.TextDocument | undefined;
readonly map: documents.TextDocument | undefined;
}
export class CompilationResult {
public readonly host: fakes.CompilerHost;
public readonly program: ts.Program | undefined;
public readonly result: ts.EmitResult | undefined;
public readonly options: ts.CompilerOptions;
public readonly diagnostics: readonly ts.Diagnostic[];
public readonly js: ReadonlyMap<string, documents.TextDocument>;
public readonly dts: ReadonlyMap<string, documents.TextDocument>;
public readonly maps: ReadonlyMap<string, documents.TextDocument>;
public symlinks?: vfs.FileSet; // Location to store original symlinks so they may be used in both original and declaration file compilations
private _inputs: documents.TextDocument[] = [];
private _inputsAndOutputs: collections.SortedMap<string, CompilationOutput>;
constructor(host: fakes.CompilerHost, options: ts.CompilerOptions, program: ts.Program | undefined, result: ts.EmitResult | undefined, diagnostics: readonly ts.Diagnostic[]) {
this.host = host;
this.program = program;
this.result = result;
this.diagnostics = diagnostics;
this.options = program ? program.getCompilerOptions() : options;
// collect outputs
const js = this.js = new collections.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
const dts = this.dts = new collections.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
const maps = this.maps = new collections.SortedMap<string, documents.TextDocument>({ comparer: this.vfs.stringComparer, sort: "insertion" });
for (const document of this.host.outputs) {
if (vpath.isJavaScript(document.file) || fileExtensionIs(document.file, ts.Extension.Json)) {
js.set(document.file, document);
}
else if (vpath.isDeclaration(document.file)) {
dts.set(document.file, document);
}
else if (vpath.isSourceMap(document.file)) {
maps.set(document.file, document);
}
}
// correlate inputs and outputs
this._inputsAndOutputs = new collections.SortedMap<string, CompilationOutput>({ comparer: this.vfs.stringComparer, sort: "insertion" });
if (program) {
if (this.options.out || this.options.outFile) {
const outFile = vpath.resolve(this.vfs.cwd(), this.options.outFile || this.options.out);
const inputs: documents.TextDocument[] = [];
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile) {
const input = new documents.TextDocument(sourceFile.fileName, sourceFile.text);
this._inputs.push(input);
if (!vpath.isDeclaration(sourceFile.fileName)) {
inputs.push(input);
}
}
}
const outputs: CompilationOutput = {
inputs,
js: js.get(outFile),
dts: dts.get(vpath.changeExtension(outFile, ".d.ts")),
map: maps.get(outFile + ".map")
};
if (outputs.js) this._inputsAndOutputs.set(outputs.js.file, outputs);
if (outputs.dts) this._inputsAndOutputs.set(outputs.dts.file, outputs);
if (outputs.map) this._inputsAndOutputs.set(outputs.map.file, outputs);
for (const input of inputs) {
this._inputsAndOutputs.set(input.file, outputs);
}
}
else {
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile) {
const input = new documents.TextDocument(sourceFile.fileName, sourceFile.text);
this._inputs.push(input);
if (!vpath.isDeclaration(sourceFile.fileName)) {
const extname = getOutputExtension(sourceFile.fileName, this.options);
const outputs: CompilationOutput = {
inputs: [input],
js: js.get(this.getOutputPath(sourceFile.fileName, extname)),
dts: dts.get(this.getOutputPath(sourceFile.fileName, getDeclarationEmitExtensionForPath(sourceFile.fileName))),
map: maps.get(this.getOutputPath(sourceFile.fileName, extname + ".map"))
};
this._inputsAndOutputs.set(sourceFile.fileName, outputs);
if (outputs.js) this._inputsAndOutputs.set(outputs.js.file, outputs);
if (outputs.dts) this._inputsAndOutputs.set(outputs.dts.file, outputs);
if (outputs.map) this._inputsAndOutputs.set(outputs.map.file, outputs);
}
}
}
}
}
}
public get vfs(): vfs.FileSystem {
return this.host.vfs;
}
public get inputs(): readonly documents.TextDocument[] {
return this._inputs;
}
public get outputs(): readonly documents.TextDocument[] {
return this.host.outputs;
}
public get traces(): readonly string[] {
return this.host.traces;
}
public get emitSkipped(): boolean {
return this.result && this.result.emitSkipped || false;
}
public get singleFile(): boolean {
return !!this.options.outFile || !!this.options.out;
}
public get commonSourceDirectory(): string {
const common = this.program && (this.program as any).getCommonSourceDirectory() || "";
return common && vpath.combine(this.vfs.cwd(), common);
}
public getInputsAndOutputs(path: string): CompilationOutput | undefined {
return this._inputsAndOutputs.get(vpath.resolve(this.vfs.cwd(), path));
}
public getInputs(path: string): readonly documents.TextDocument[] | undefined {
const outputs = this.getInputsAndOutputs(path);
return outputs && outputs.inputs;
}
public getOutput(path: string, kind: "js" | "dts" | "map"): documents.TextDocument | undefined {
const outputs = this.getInputsAndOutputs(path);
return outputs && outputs[kind];
}
public getOutputPath(path: string, ext: string): string {
if (this.options.outFile || this.options.out) {
path = vpath.resolve(this.vfs.cwd(), this.options.outFile || this.options.out);
}
else {
path = vpath.resolve(this.vfs.cwd(), path);
const outDir = ext === ".d.ts" || ext === ".json.d.ts" || ext === ".d.mts" || ext === ".d.cts" ? this.options.declarationDir || this.options.outDir : this.options.outDir;
if (outDir) {
const common = this.commonSourceDirectory;
if (common) {
path = vpath.relative(common, path, this.vfs.ignoreCase);
path = vpath.combine(vpath.resolve(this.vfs.cwd(), this.options.outDir), path);
}
}
}
return vpath.changeExtension(path, ext);
}
public getNumberOfJsFiles(includeJson: boolean) {
if (includeJson) {
return this.js.size;
}
else {
let count = this.js.size;
this.js.forEach(document => {
if (fileExtensionIs(document.file, ts.Extension.Json)) {
count--;
}
});
return count;
}
}
}
export function compileFiles(host: fakes.CompilerHost, rootFiles: string[] | undefined, compilerOptions: ts.CompilerOptions): CompilationResult {
if (compilerOptions.project || !rootFiles || rootFiles.length === 0) {
const project = readProject(host.parseConfigHost, compilerOptions.project, compilerOptions);
if (project) {
if (project.errors && project.errors.length > 0) {
return new CompilationResult(host, compilerOptions, /*program*/ undefined, /*result*/ undefined, project.errors);
}
if (project.config) {
rootFiles = project.config.fileNames;
compilerOptions = project.config.options;
}
}
delete compilerOptions.project;
}
// establish defaults (aligns with old harness)
if (compilerOptions.target === undefined && compilerOptions.module !== ts.ModuleKind.Node16 && compilerOptions.module !== ts.ModuleKind.NodeNext) compilerOptions.target = ts.ScriptTarget.ES3;
if (compilerOptions.newLine === undefined) compilerOptions.newLine = ts.NewLineKind.CarriageReturnLineFeed;
if (compilerOptions.skipDefaultLibCheck === undefined) compilerOptions.skipDefaultLibCheck = true;
if (compilerOptions.noErrorTruncation === undefined) compilerOptions.noErrorTruncation = true;
// pre-emit/post-emit error comparison requires declaration emit twice, which can be slow. If it's unlikely to flag any error consistency issues
// and if the test is running `skipLibCheck` - an indicator that we want the tets to run quickly - skip the before/after error comparison, too
const skipErrorComparison = lang.length(rootFiles) >= 100 || (!!compilerOptions.skipLibCheck && !!compilerOptions.declaration);
const preProgram = !skipErrorComparison ? ts.createProgram(rootFiles || [], { ...compilerOptions, configFile: compilerOptions.configFile, traceResolution: false }, host) : undefined;
const preErrors = preProgram && ts.getPreEmitDiagnostics(preProgram);
const program = ts.createProgram(rootFiles || [], compilerOptions, host);
const emitResult = program.emit(undefined, (fileName, text, writeByteOrderMark) => {
// If there are errors TS will ot emit declaration files. We want then regardless of errors.
if(!vpath.isAbsolute(fileName)) {
fileName = vpath.resolve(host.vfs.cwd(), fileName);
}
if(host.outputs.some(d => d.file === fileName)) return;
host.writeFile(fileName, text, writeByteOrderMark);
// @ts-expect-error We use forceDts emit documented flag
}, undefined, undefined,undefined, true);
const postErrors = ts.getPreEmitDiagnostics(program);
const longerErrors = lang.length(preErrors) > postErrors.length ? preErrors : postErrors;
const shorterErrors = longerErrors === preErrors ? postErrors : preErrors;
const errors = preErrors && (preErrors.length !== postErrors.length) ? [...shorterErrors!] : postErrors;
return new CompilationResult(host, compilerOptions, program, emitResult, errors);
}

View File

@@ -0,0 +1,409 @@
import * as ts from 'typescript';
import * as vfs from "./vfs";
import * as vpath from "./vpath";
import * as collections from './collections'
import * as documents from './test-document'
import { Utils } from './compiler-run';
import { getNewLineCharacter } from '../../compiler/utils';
/**
* Fake implementations of various compiler dependencies.
*/
const processExitSentinel = new Error("System exit");
export interface SystemOptions {
executingFilePath?: string;
newLine?: "\r\n" | "\n";
env?: Record<string, string>;
}
/**
* A fake `ts.System` that leverages a virtual file system.
*/
export class System implements ts.System {
public readonly vfs: vfs.FileSystem;
public readonly args: string[] = [];
public readonly output: string[] = [];
public readonly newLine: string;
public readonly useCaseSensitiveFileNames: boolean;
public exitCode: number | undefined;
private readonly _executingFilePath: string | undefined;
private readonly _env: Record<string, string> | undefined;
constructor(vfs: vfs.FileSystem, { executingFilePath, newLine = "\r\n", env }: SystemOptions = {}) {
this.vfs = vfs.isReadonly ? vfs.shadow() : vfs;
this.useCaseSensitiveFileNames = !this.vfs.ignoreCase;
this.newLine = newLine;
this._executingFilePath = executingFilePath;
this._env = env;
}
private testTerminalWidth = Number.parseInt(this.getEnvironmentVariable("TS_TEST_TERMINAL_WIDTH"));
getWidthOfTerminal = Number.isNaN(this.testTerminalWidth) ? undefined : () => this.testTerminalWidth;
// Pretty output
writeOutputIsTTY() {
return true;
}
public write(message: string) {
console.log(message);
this.output.push(message);
}
public readFile(path: string) {
try {
const content = this.vfs.readFileSync(path, "utf8");
return content === undefined ? undefined : Utils.removeByteOrderMark(content);
}
catch {
return undefined;
}
}
public writeFile(path: string, data: string, writeByteOrderMark?: boolean): void {
this.vfs.mkdirpSync(vpath.dirname(path));
this.vfs.writeFileSync(path, writeByteOrderMark ? Utils.addUTF8ByteOrderMark(data) : data);
}
public deleteFile(path: string) {
this.vfs.unlinkSync(path);
}
public fileExists(path: string) {
const stats = this._getStats(path);
return stats ? stats.isFile() : false;
}
public directoryExists(path: string) {
const stats = this._getStats(path);
return stats ? stats.isDirectory() : false;
}
public createDirectory(path: string): void {
this.vfs.mkdirpSync(path);
}
public getCurrentDirectory() {
return this.vfs.cwd();
}
public getDirectories(path: string) {
const result: string[] = [];
try {
for (const file of this.vfs.readdirSync(path)) {
if (this.vfs.statSync(vpath.combine(path, file)).isDirectory()) {
result.push(file);
}
}
}
catch { /*ignore*/ }
return result;
}
public readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] {
throw new Error("Not implemented");
// return matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, path => this.getAccessibleFileSystemEntries(path), path => this.realpath(path));
}
public getAccessibleFileSystemEntries(path: string): vfs.FileSystemEntries {
const files: string[] = [];
const directories: string[] = [];
try {
for (const file of this.vfs.readdirSync(path)) {
try {
const stats = this.vfs.statSync(vpath.combine(path, file));
if (stats.isFile()) {
files.push(file);
}
else if (stats.isDirectory()) {
directories.push(file);
}
}
catch { /*ignored*/ }
}
}
catch { /*ignored*/ }
return { files, directories };
}
public exit(exitCode?: number) {
this.exitCode = exitCode;
throw processExitSentinel;
}
public getFileSize(path: string) {
const stats = this._getStats(path);
return stats && stats.isFile() ? stats.size : 0;
}
public resolvePath(path: string) {
return vpath.resolve(this.vfs.cwd(), path);
}
public getExecutingFilePath() {
if (this._executingFilePath === undefined) throw new Error("ts.notImplemented");
return this._executingFilePath;
}
public getModifiedTime(path: string) {
const stats = this._getStats(path);
return stats ? stats.mtime : undefined!; // TODO: GH#18217
}
public setModifiedTime(path: string, time: Date) {
this.vfs.utimesSync(path, time, time);
}
public createHash(data: string): string {
return `${generateDjb2Hash(data)}-${data}`;
}
public realpath(path: string) {
try {
return this.vfs.realpathSync(path);
}
catch {
return path;
}
}
public getEnvironmentVariable(name: string): string {
return (this._env && this._env[name])!; // TODO: GH#18217
}
private _getStats(path: string) {
try {
return this.vfs.existsSync(path) ? this.vfs.statSync(path) : undefined;
}
catch {
return undefined;
}
}
now() {
return new Date(this.vfs.time());
}
}
/**
* A fake `ts.ParseConfigHost` that leverages a virtual file system.
*/
export class ParseConfigHost implements ts.ParseConfigHost {
public readonly sys: System;
constructor(sys: System | vfs.FileSystem) {
if (sys instanceof vfs.FileSystem) sys = new System(sys);
this.sys = sys;
}
public get vfs() {
return this.sys.vfs;
}
public get useCaseSensitiveFileNames() {
return this.sys.useCaseSensitiveFileNames;
}
public fileExists(fileName: string): boolean {
return this.sys.fileExists(fileName);
}
public directoryExists(directoryName: string): boolean {
return this.sys.directoryExists(directoryName);
}
public readFile(path: string): string | undefined {
return this.sys.readFile(path);
}
public readDirectory(path: string, extensions: string[], excludes: string[], includes: string[], depth: number): string[] {
return this.sys.readDirectory(path, extensions, excludes, includes, depth);
}
}
/**
* A fake `ts.CompilerHost` that leverages a virtual file system.
*/
export class CompilerHost implements ts.CompilerHost {
public readonly sys: System;
public readonly defaultLibLocation: string;
public readonly outputs: documents.TextDocument[] = [];
private readonly _outputsMap: collections.SortedMap<string, number>;
public readonly traces: string[] = [];
public readonly shouldAssertInvariants = false;
private _setParentNodes: boolean;
private _sourceFiles: collections.SortedMap<string, ts.SourceFile>;
private _parseConfigHost: ParseConfigHost | undefined;
private _newLine: string;
constructor(sys: System | vfs.FileSystem, options = ts.getDefaultCompilerOptions(), setParentNodes = false) {
if (sys instanceof vfs.FileSystem) sys = new System(sys);
this.sys = sys;
this.defaultLibLocation = sys.vfs.meta.get("defaultLibLocation") || "";
this._newLine = getNewLineCharacter(options, () => this.sys.newLine);
this._sourceFiles = new collections.SortedMap<string, ts.SourceFile>({ comparer: sys.vfs.stringComparer, sort: "insertion" });
this._setParentNodes = setParentNodes;
this._outputsMap = new collections.SortedMap(this.vfs.stringComparer);
}
public get vfs() {
return this.sys.vfs;
}
public get parseConfigHost() {
return this._parseConfigHost || (this._parseConfigHost = new ParseConfigHost(this.sys));
}
public getCurrentDirectory(): string {
return this.sys.getCurrentDirectory();
}
public useCaseSensitiveFileNames(): boolean {
return this.sys.useCaseSensitiveFileNames;
}
public getNewLine(): string {
return this._newLine;
}
public getCanonicalFileName(fileName: string): string {
return this.sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
}
public deleteFile(fileName: string) {
this.sys.deleteFile(fileName);
}
public fileExists(fileName: string): boolean {
return this.sys.fileExists(fileName);
}
public directoryExists(directoryName: string): boolean {
return this.sys.directoryExists(directoryName);
}
public getModifiedTime(fileName: string) {
return this.sys.getModifiedTime(fileName);
}
public setModifiedTime(fileName: string, time: Date) {
return this.sys.setModifiedTime(fileName, time);
}
public getDirectories(path: string): string[] {
return this.sys.getDirectories(path);
}
public readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] {
return this.sys.readDirectory(path, extensions, exclude, include, depth);
}
public readFile(path: string): string | undefined {
return this.sys.readFile(path);
}
public writeFile(fileName: string, content: string, writeByteOrderMark: boolean) {
if (writeByteOrderMark) content = Utils.addUTF8ByteOrderMark(content);
this.sys.writeFile(fileName, content);
const document = new documents.TextDocument(fileName, content);
document.meta.set("fileName", fileName);
this.vfs.filemeta(fileName).set("document", document);
if (!this._outputsMap.has(document.file)) {
this._outputsMap.set(document.file, this.outputs.length);
this.outputs.push(document);
}
this.outputs[this._outputsMap.get(document.file)!] = document;
}
public trace(s: string): void {
this.traces.push(s);
}
public realpath(path: string): string {
return this.sys.realpath(path);
}
public getDefaultLibLocation(): string {
return vpath.resolve(this.getCurrentDirectory(), this.defaultLibLocation);
}
public getDefaultLibFileName(options: ts.CompilerOptions): string {
return vpath.resolve(this.getDefaultLibLocation(), ts.getDefaultLibFileName(options));
}
public getSourceFile(fileName: string, languageVersion: number): ts.SourceFile | undefined {
const canonicalFileName = this.getCanonicalFileName(vpath.resolve(this.getCurrentDirectory(), fileName));
const existing = this._sourceFiles.get(canonicalFileName);
if (existing) return existing;
const content = this.readFile(canonicalFileName);
if (content === undefined) return undefined;
// A virtual file system may shadow another existing virtual file system. This
// allows us to reuse a common virtual file system structure across multiple
// tests. If a virtual file is a shadow, it is likely that the file will be
// reused across multiple tests. In that case, we cache the SourceFile we parse
// so that it can be reused across multiple tests to avoid the cost of
// repeatedly parsing the same file over and over (such as lib.d.ts).
const cacheKey = this.vfs.shadowRoot && `SourceFile[languageVersion=${languageVersion},setParentNodes=${this._setParentNodes}]`;
if (cacheKey) {
const meta = this.vfs.filemeta(canonicalFileName);
const sourceFileFromMetadata = meta.get(cacheKey) as ts.SourceFile | undefined;
if (sourceFileFromMetadata && sourceFileFromMetadata.getFullText() === content) {
this._sourceFiles.set(canonicalFileName, sourceFileFromMetadata);
return sourceFileFromMetadata;
}
}
const parsed = ts.createSourceFile(fileName, content, languageVersion, this._setParentNodes || this.shouldAssertInvariants);
this._sourceFiles.set(canonicalFileName, parsed);
if (cacheKey) {
// store the cached source file on the unshadowed file with the same version.
const stats = this.vfs.statSync(canonicalFileName);
let fs = this.vfs;
while (fs.shadowRoot) {
try {
const shadowRootStats = fs.shadowRoot.existsSync(canonicalFileName) ? fs.shadowRoot.statSync(canonicalFileName) : undefined!; // TODO: GH#18217
if (shadowRootStats.dev !== stats.dev ||
shadowRootStats.ino !== stats.ino ||
shadowRootStats.mtimeMs !== stats.mtimeMs) {
break;
}
fs = fs.shadowRoot;
}
catch {
break;
}
}
if (fs !== this.vfs) {
fs.filemeta(canonicalFileName).set(cacheKey, parsed);
}
}
return parsed;
}
}
/**
* djb2 hashing algorithm
* http://www.cse.yorku.ca/~oz/hash.html
*
* @internal
*/
export function generateDjb2Hash(data: string): string {
let acc = 5381;
for (let i = 0; i < data.length; i++) {
acc = ((acc << 5) + acc) + data.charCodeAt(i);
}
return acc.toString();
}

View File

@@ -0,0 +1,177 @@
import { sys } from "typescript";
import { FileSystemEntries } from "./vfs";
import * as vpath from './vpath';
import { compareStringsCaseSensitive, compareStringsCaseInsensitive } from "../../compiler/lang-utils";
type RunnerBase = unknown;
export interface IO {
newLine(): string;
getCurrentDirectory(): string;
useCaseSensitiveFileNames(): boolean;
resolvePath(path: string): string | undefined;
getFileSize(path: string): number;
readFile(path: string): string | undefined;
writeFile(path: string, contents: string): void;
directoryName(path: string): string | undefined;
getDirectories(path: string): string[];
createDirectory(path: string): void;
fileExists(fileName: string): boolean;
directoryExists(path: string): boolean;
deleteFile(fileName: string): void;
listFiles(path: string, filter?: RegExp, options?: { recursive?: boolean }): string[];
log(text: string): void;
args(): string[];
getExecutingFilePath(): string;
getWorkspaceRoot(): string;
exit(exitCode?: number): void;
readDirectory(path: string, extension?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): readonly string[];
getAccessibleFileSystemEntries(dirname: string): FileSystemEntries;
tryEnableSourceMapsForHost?(): void;
getEnvironmentVariable?(name: string): string;
getMemoryUsage?(): number | undefined;
joinPath(...components: string[]): string
}
export let IO: IO;
export function setHarnessIO(io: IO) {
IO = io;
}
// harness always uses one kind of new line
// But note that `parseTestData` in `fourslash.ts` uses "\n"
export const harnessNewLine = "\r\n";
// Root for file paths that are stored in a virtual file system
export const virtualFileSystemRoot = "/";
function createNodeIO(): IO {
let workspaceRoot = "../";
let fs: any, pathModule: any;
if (require) {
fs = require("fs");
pathModule = require("path");
workspaceRoot = pathModule.resolve(workspaceRoot);
}
else {
fs = pathModule = {};
}
function deleteFile(path: string) {
try {
fs.unlinkSync(path);
}
catch { /*ignore*/ }
}
function directoryName(path: string) {
const dirPath = pathModule.dirname(path);
// Node will just continue to repeat the root path, rather than return null
return dirPath === path ? undefined : dirPath;
}
function joinPath(...components: string[]) {
return pathModule.join(...components);
}
function enumerateTestFiles(runner: RunnerBase):any[] {
throw new Error("Not implemented");
// return runner.getTestFiles();
}
function listFiles(path: string, spec: RegExp, options: { recursive?: boolean } = {}) {
function filesInFolder(folder: string): string[] {
let paths: string[] = [];
for (const file of fs.readdirSync(folder)) {
const pathToFile = pathModule.join(folder, file);
if (!fs.existsSync(pathToFile)) continue; // ignore invalid symlinks
const stat = fs.statSync(pathToFile);
if (options.recursive && stat.isDirectory()) {
paths = paths.concat(filesInFolder(pathToFile));
}
else if (stat.isFile() && (!spec || file.match(spec))) {
paths.push(pathToFile);
}
}
return paths;
}
return filesInFolder(path);
}
function getAccessibleFileSystemEntries(dirname: string): FileSystemEntries {
try {
const entries: string[] = fs.readdirSync(dirname || ".").sort(sys.useCaseSensitiveFileNames ? compareStringsCaseSensitive : compareStringsCaseInsensitive);
const files: string[] = [];
const directories: string[] = [];
for (const entry of entries) {
if (entry === "." || entry === "..") continue;
const name = vpath.combine(dirname, entry);
try {
const stat = fs.statSync(name);
if (!stat) continue;
if (stat.isFile()) {
files.push(entry);
}
else if (stat.isDirectory()) {
directories.push(entry);
}
}
catch { /*ignore*/ }
}
return { files, directories };
}
catch (e) {
return { files: [], directories: [] };
}
}
function createDirectory(path: string) {
try {
fs.mkdirSync(path);
}
catch (e) {
if (e.code === "ENOENT") {
createDirectory(vpath.dirname(path));
createDirectory(path);
}
else if (!sys.directoryExists(path)) {
throw e;
}
}
}
return {
newLine: () => harnessNewLine,
getCurrentDirectory: () => sys.getCurrentDirectory(),
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
resolvePath: (path: string) => sys.resolvePath(path),
getFileSize: (path: string) => sys.getFileSize!(path),
readFile: path => sys.readFile(path),
writeFile: (path, content) => sys.writeFile(path, content),
directoryName,
getDirectories: path => sys.getDirectories(path),
createDirectory,
fileExists: path => sys.fileExists(path),
directoryExists: path => sys.directoryExists(path),
deleteFile,
listFiles,
log: s => console.log(s),
args: () => sys.args,
getExecutingFilePath: () => sys.getExecutingFilePath(),
getWorkspaceRoot: () => workspaceRoot,
exit: exitCode => sys.exit(exitCode),
readDirectory: (path, extension, exclude, include, depth) => sys.readDirectory(path, extension, exclude, include, depth),
getAccessibleFileSystemEntries,
tryEnableSourceMapsForHost: () => { throw new Error("Not supported")},
getMemoryUsage: () => sys.getMemoryUsage && sys.getMemoryUsage(),
getEnvironmentVariable(name: string) {
return process.env[name] || "";
},
joinPath
};
}
IO = createNodeIO();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
import { isLineBreak } from "typescript";
import { CharacterCodes } from "../../compiler/types";
import { TestFile } from "./compiler-run";
/** @internal */
export function computeLineStarts(text: string): number[] {
const result: number[] = [];
let pos = 0;
let lineStart = 0;
while (pos < text.length) {
const ch = text.charCodeAt(pos);
pos++;
switch (ch) {
case CharacterCodes.carriageReturn:
if (text.charCodeAt(pos) === CharacterCodes.lineFeed) {
pos++;
}
// falls through
case CharacterCodes.lineFeed:
result.push(lineStart);
lineStart = pos;
break;
default:
if (ch > CharacterCodes.maxAsciiCharacter && isLineBreak(ch)) {
result.push(lineStart);
lineStart = pos;
}
break;
}
}
result.push(lineStart);
return result;
}
export class TextDocument {
public readonly meta: Map<string, string>;
public readonly file: string;
public readonly text: string;
private _lineStarts: readonly number[] | undefined;
private _testFile: TestFile | undefined;
constructor(file: string, text: string, meta?: Map<string, string>) {
this.file = file;
this.text = text;
this.meta = meta || new Map<string, string>();
}
public get lineStarts(): readonly number[] {
return this._lineStarts || (this._lineStarts = computeLineStarts(this.text));
}
public static fromTestFile(file: TestFile) {
return new TextDocument(
file.unitName,
file.content,
file.fileOptions && Object.keys(file.fileOptions)
.reduce((meta, key) => meta.set(key, file.fileOptions[key]), new Map<string, string>()));
}
public asTestFile() {
return this._testFile || (this._testFile = {
unitName: this.file,
content: this.text,
fileOptions: Array.from(this.meta)
.reduce((obj, [key, value]) => (obj[key] = value, obj), {} as Record<string, string>)
});
}
}

View File

@@ -0,0 +1,216 @@
import assert = require('assert');
import * as ts from 'typescript'
import { find, forEach, orderedRemoveItemAt } from '../../compiler/lang-utils';
import { getBaseFileName, getDirectoryPath, getNormalizedAbsolutePath, normalizePath } from '../../compiler/path-utils';
import { _Path } from '../../compiler/types';
import * as vfs from './vfs'
/** all the necessary information to set the right compiler settings */
export interface CompilerSettings {
[name: string]: string;
}
/** All the necessary information to turn a multi file test into useful units for later compilation */
export interface TestUnitData {
content: string;
name: string;
fileOptions: any;
originalFilePath: string;
references: string[];
startLine: number;
endLine: number;
}
export interface ParseConfigHost {
useCaseSensitiveFileNames: boolean;
readDirectory(rootDir: string, extensions: readonly string[], excludes: readonly string[] | undefined, includes: readonly string[], depth?: number): readonly string[];
/**
* Gets a value indicating whether the specified path exists and is a file.
* @param path The path to test.
*/
fileExists(path: string): boolean;
readFile(path: string): string | undefined;
trace?(s: string): void;
}
const optionRegex = /^[\/]{2}\s*@(\w+)\s*:\s*([^\r\n]*)/gm; // multiple matches on multiple lines
const linkRegex = /^[\/]{2}\s*@link\s*:\s*([^\r\n]*)\s*->\s*([^\r\n]*)/gm; // multiple matches on multiple lines
export function extractCompilerSettings(content: string): CompilerSettings {
const opts: CompilerSettings = {};
let match: RegExpExecArray | null;
while ((match = optionRegex.exec(content)) !== null) { // eslint-disable-line no-null/no-null
opts[match[1]] = match[2].trim();
}
return opts;
}
/** Splits the given string on \r\n, or on only \n if that fails, or on only \r if *that* fails. */
export function splitContentByNewlines(content: string) {
const split = (delimiter: string) => Object.assign(content.split(delimiter), { delimiter });
// Split up the input file by line
// Note: IE JS engine incorrectly handles consecutive delimiters here when using RegExp split, so
// we have to use string-based splitting instead and try to figure out the delimiting chars
let lines = split("\r\n");
if (lines.length === 1) {
lines = split("\n");
if (lines.length === 1) {
lines = split("\r");
}
}
return lines;
}
export function getConfigNameFromFileName(filename: string): "tsconfig.json" | "jsconfig.json" | undefined {
const flc = getBaseFileName(filename).toLowerCase();
return find(["tsconfig.json" as const, "jsconfig.json" as const], x => x === flc);
}
export function parseSymlinkFromTest(line: string, symlinks: vfs.FileSet | undefined) {
const linkMetaData = linkRegex.exec(line);
linkRegex.lastIndex = 0;
if (!linkMetaData) return undefined;
if (!symlinks) symlinks = {};
symlinks[linkMetaData[2].trim()] = new vfs.Symlink(linkMetaData[1].trim());
return symlinks;
}
export interface TestCaseContent {
settings: CompilerSettings;
testUnitData: TestUnitData[];
tsConfig: ts.ParsedCommandLine | undefined;
tsConfigFileUnitData: TestUnitData | undefined;
symlinks?: vfs.FileSet;
code: string;
}
/** Given a test file containing // @FileName directives, return an array of named units of code to be added to an existing compiler instance */
export function makeUnitsFromTest(code: string, fileName: string, rootDir?: string, settings = extractCompilerSettings(code)): TestCaseContent {
// List of all the subfiles we've parsed out
const testUnitData: TestUnitData[] = [];
const lines = splitContentByNewlines(code);
// Stuff related to the subfile we're parsing
let currentFileContent: string | undefined;
let currentFileOptions: any = {};
let currentFileName: any;
let currentFileStartLine: number = 0;
let currentFileEndLine: number = 0;
let refs: string[] = [];
let symlinks: vfs.FileSet | undefined;
let lineIndex = -1;
for (const line of lines) {
lineIndex++;
let testMetaData: RegExpExecArray | null;
const possiblySymlinks = parseSymlinkFromTest(line, symlinks);
if (possiblySymlinks) {
symlinks = possiblySymlinks;
}
else if (testMetaData = optionRegex.exec(line)) {
// Comment line, check for global/file @options and record them
optionRegex.lastIndex = 0;
const metaDataName = testMetaData[1].toLowerCase();
currentFileOptions[testMetaData[1]] = testMetaData[2].trim();
if (metaDataName !== "filename") {
continue;
}
// New metadata statement after having collected some code to go with the previous metadata
if (currentFileName) {
// Store result file
const newTestFile = {
content: currentFileContent!, // TODO: GH#18217
name: currentFileName,
fileOptions: currentFileOptions,
originalFilePath: fileName,
references: refs,
startLine: currentFileStartLine,
endLine: currentFileEndLine,
};
testUnitData.push(newTestFile);
// Reset local data
currentFileContent = undefined;
currentFileOptions = {};
currentFileName = testMetaData[2].trim();
currentFileStartLine = lineIndex + 1;
refs = [];
}
else {
// First metadata marker in the file
currentFileName = testMetaData[2].trim();
currentFileStartLine = lineIndex + 1;
currentFileContent = undefined;
}
}
else {
currentFileEndLine = lineIndex;
// Subfile content line
// Append to the current subfile content, inserting a newline needed
if (currentFileContent === undefined) {
currentFileContent = "";
currentFileStartLine = lineIndex;
}
else {
// End-of-line
currentFileContent = currentFileContent + "\n";
}
currentFileContent = currentFileContent + line;
}
}
// normalize the fileName for the single file case
currentFileName = testUnitData.length > 0 || currentFileName ? currentFileName : getBaseFileName(fileName);
// EOF, push whatever remains
const newTestFile2 = {
content: currentFileContent || "",
name: currentFileName,
fileOptions: currentFileOptions,
originalFilePath: fileName,
references: refs,
startLine: currentFileStartLine,
endLine: currentFileEndLine,
};
testUnitData.push(newTestFile2);
// unit tests always list files explicitly
const parseConfigHost: ts.ParseConfigHost = {
useCaseSensitiveFileNames: false,
readDirectory: () => [],
fileExists: () => true,
readFile: (name) => forEach(testUnitData, data => data.name.toLowerCase() === name.toLowerCase() ? data.content : undefined)
};
// check if project has tsconfig.json in the list of files
let tsConfig: ts.ParsedCommandLine | undefined;
let tsConfigFileUnitData: TestUnitData | undefined;
for (let i = 0; i < testUnitData.length; i++) {
const data = testUnitData[i];
if (getConfigNameFromFileName(data.name)) {
const configJson = ts.parseJsonText(data.name, data.content);
let baseDir = normalizePath(getDirectoryPath(data.name));
if (rootDir) {
baseDir = getNormalizedAbsolutePath(baseDir, rootDir);
}
tsConfig = ts.parseJsonSourceFileConfigFileContent(configJson, parseConfigHost, baseDir);
tsConfig.options.configFilePath = data.name as _Path;
tsConfigFileUnitData = data;
// delete entry from the list
orderedRemoveItemAt(testUnitData, i);
break;
}
}
return { settings, testUnitData, tsConfig, tsConfigFileUnitData, symlinks, code };
}

View File

@@ -0,0 +1,174 @@
import { hasProperty } from "../../compiler/debug";
import { equateStringsCaseInsensitive, map, forEach, startsWith, findIndex, arrayFrom, orderedRemoveItemAt, getEntries } from "../../compiler/lang-utils";
import { optionDeclarations } from "./options";
import { CompilerSettings } from "./test-file-parser";
export interface FileBasedTestConfiguration {
[key: string]: string;
}
const varyBy: readonly string[] = [
"module",
"moduleResolution",
"moduleDetection",
"target",
"jsx",
"removeComments",
"importHelpers",
"importHelpers",
"downlevelIteration",
"isolatedModules",
"strict",
"noImplicitAny",
"strictNullChecks",
"strictFunctionTypes",
"strictBindCallApply",
"strictPropertyInitialization",
"noImplicitThis",
"alwaysStrict",
"allowSyntheticDefaultImports",
"esModuleInterop",
"emitDecoratorMetadata",
"skipDefaultLibCheck",
"preserveConstEnums",
"skipLibCheck",
"exactOptionalPropertyTypes",
"useDefineForClassFields",
"useUnknownInCatchVariables",
"noUncheckedIndexedAccess",
"noPropertyAccessFromIndexSignature",
];
/**
* Compute FileBasedTestConfiguration variations based on a supplied list of variable settings.
*/
export function getFileBasedTestConfigurations(settings: CompilerSettings): FileBasedTestConfiguration[] | undefined {
let varyByEntries: [string, string[]][] | undefined;
let variationCount = 1;
for (const varyByKey of varyBy) {
if (hasProperty(settings, varyByKey)) {
// we only consider variations when there are 2 or more variable entries.
const entries = splitVaryBySettingValue(settings[varyByKey], varyByKey);
if (entries) {
if (!varyByEntries) varyByEntries = [];
variationCount *= entries.length;
if (variationCount > 25) throw new Error(`Provided test options exceeded the maximum number of variations: ${varyBy.map(v => `'@${v}'`).join(", ")}`);
varyByEntries.push([varyByKey, entries]);
}
}
}
if (!varyByEntries) return undefined;
const configurations: FileBasedTestConfiguration[] = [];
computeFileBasedTestConfigurationVariations(configurations, /*variationState*/ {}, varyByEntries, /*offset*/ 0);
return configurations;
}
function computeFileBasedTestConfigurationVariations(configurations: FileBasedTestConfiguration[], variationState: FileBasedTestConfiguration, varyByEntries: [string, string[]][], offset: number) {
if (offset >= varyByEntries.length) {
// make a copy of the current variation state
configurations.push({ ...variationState });
return;
}
const [varyBy, entries] = varyByEntries[offset];
for (const entry of entries) {
// set or overwrite the variation, then compute the next variation
variationState[varyBy] = entry;
computeFileBasedTestConfigurationVariations(configurations, variationState, varyByEntries, offset + 1);
}
}
function splitVaryBySettingValue(text: string, varyBy: string): string[] | undefined {
if (!text) return undefined;
let star = false;
const includes: string[] = [];
const excludes: string[] = [];
for (let s of text.split(/,/g)) {
s = s.trim().toLowerCase();
if (s.length === 0) continue;
if (s === "*") {
star = true;
}
else if (startsWith(s, "-") || startsWith(s, "!")) {
excludes.push(s.slice(1));
}
else {
includes.push(s);
}
}
// do nothing if the setting has no variations
if (includes.length <= 1 && !star && excludes.length === 0) {
return undefined;
}
const variations: { key: string, value?: string | number }[] = [];
const values = getVaryByStarSettingValues(varyBy);
// add (and deduplicate) all included entries
for (const include of includes) {
const value = values?.get(include);
if (findIndex(variations, v => v.key === include || value !== undefined && v.value === value) === -1) {
variations.push({ key: include, value });
}
}
if (star && values) {
// add all entries
for (const [key, value] of arrayFrom(values.entries())) {
if (findIndex(variations, v => v.key === key || v.value === value) === -1) {
variations.push({ key, value });
}
}
}
// remove all excluded entries
for (const exclude of excludes) {
const value = values?.get(exclude);
let index: number;
while ((index = findIndex(variations, v => v.key === exclude || value !== undefined && v.value === value)) >= 0) {
orderedRemoveItemAt(variations, index);
}
}
if (variations.length === 0) {
throw new Error(`Variations in test option '@${varyBy}' resulted in an empty set.`);
}
return map(variations, v => v.key);
}
let booleanVaryByStarSettingValues: Map<string, string | number> | undefined;
function getVaryByStarSettingValues(varyBy: string): ReadonlyMap<string, string | number> | undefined {
const option = forEach(optionDeclarations, decl => equateStringsCaseInsensitive(decl.name, varyBy) ? decl : undefined);
if (option) {
if (typeof option.type === "object") {
return option.type;
}
if (option.type === "boolean") {
return booleanVaryByStarSettingValues || (booleanVaryByStarSettingValues = new Map(getEntries({
true: 1,
false: 0
})));
}
}
}
/**
* Compute a description for this configuration based on its entries
*/
export function getFileBasedTestConfigurationDescription(configuration: FileBasedTestConfiguration) {
let name = "";
if (configuration) {
const keys = Object.keys(configuration).sort();
for (const key of keys) {
if (name) name += ",";
name += `${key}=${configuration[key]}`;
}
}
return name;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,349 @@
import { Path } from "typescript";
import { changeAnyExtension, comparePaths, comparePathsCaseInsensitive, comparePathsCaseSensitive, getAnyExtensionFromPath, getBaseFileName, getDirectoryPath, getPathComponents, getPathComponentsRelativeTo, getPathFromPathComponents, getRelativePathFromDirectory, isDiskPathRoot, isRootedDiskPath, reducePathComponents, resolvePath } from "../../compiler/path-utils";
import { CharacterCodes } from "../../compiler/types";
import { hasJSFileExtension, hasTSFileExtension, isDeclarationFileName } from "../../compiler/utils";
import * as vfs from './vfs'
/**
* Internally, we represent paths as strings with '/' as the directory separator.
* When we make system calls (eg: LanguageServiceHost.getDirectory()),
* we expect the host to correctly handle paths in our specified format.
*
* @internal
*/
export const directorySeparator = "/";
/** @internal */
export const altDirectorySeparator = "\\";
const urlSchemeSeparator = "://";
const backslashRegExp = /\\/g;
export const sep = directorySeparator;
/**
* Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified.
*
* ```ts
* // Non-rooted
* combinePaths("path", "to", "file.ext") === "path/to/file.ext"
* combinePaths("path", "dir", "..", "to", "file.ext") === "path/dir/../to/file.ext"
* // POSIX
* combinePaths("/path", "to", "file.ext") === "/path/to/file.ext"
* combinePaths("/path", "/to", "file.ext") === "/to/file.ext"
* // DOS
* combinePaths("c:/path", "to", "file.ext") === "c:/path/to/file.ext"
* combinePaths("c:/path", "c:/to", "file.ext") === "c:/to/file.ext"
* // URL
* combinePaths("file:///path", "to", "file.ext") === "file:///path/to/file.ext"
* combinePaths("file:///path", "file:///to", "file.ext") === "file:///to/file.ext"
* ```
*
* @internal
*/
export function combine(path: string, ...paths: (string | undefined)[]): string {
if (path) path = normalizeSlashes(path);
for (let relativePath of paths) {
if (!relativePath) continue;
relativePath = normalizeSlashes(relativePath);
if (!path || getRootLength(relativePath) !== 0) {
path = relativePath;
}
else {
path = ensureTrailingDirectorySeparator(path) + relativePath;
}
}
return path;
}
/**
* Normalize path separators, converting `\` into `/`.
*
* @internal
*/
export function normalizeSlashes(path: string): string {
return path.indexOf("\\") !== -1
? path.replace(backslashRegExp, directorySeparator)
: path;
}
/**
* Adds a trailing directory separator to a path, if it does not already have one.
*
* ```ts
* ensureTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext/"
* ensureTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext/"
* ```
*
* @internal
*/
export function ensureTrailingDirectorySeparator(path: Path): Path;
/** @internal */
export function ensureTrailingDirectorySeparator(path: string): string;
/** @internal */
export function ensureTrailingDirectorySeparator(path: string) {
if (!hasTrailingDirectorySeparator(path)) {
return path + directorySeparator;
}
return path;
}
/**
* Determines whether a path has a trailing separator (`/` or `\\`).
*
* @internal
*/
export function hasTrailingDirectorySeparator(path: string) {
return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1));
}
/**
* Determines whether a charCode corresponds to `/` or `\`.
*
* @internal
*/
export function isAnyDirectorySeparator(charCode: number): boolean {
return charCode === CharacterCodes.slash || charCode === CharacterCodes.backslash;
}
/**
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
*
* For example:
* ```ts
* getRootLength("a") === 0 // ""
* getRootLength("/") === 1 // "/"
* getRootLength("c:") === 2 // "c:"
* getRootLength("c:d") === 0 // ""
* getRootLength("c:/") === 3 // "c:/"
* getRootLength("c:\\") === 3 // "c:\\"
* getRootLength("//server") === 7 // "//server"
* getRootLength("//server/share") === 8 // "//server/"
* getRootLength("\\\\server") === 7 // "\\\\server"
* getRootLength("\\\\server\\share") === 8 // "\\\\server\\"
* getRootLength("file:///path") === 8 // "file:///"
* getRootLength("file:///c:") === 10 // "file:///c:"
* getRootLength("file:///c:d") === 8 // "file:///"
* getRootLength("file:///c:/path") === 11 // "file:///c:/"
* getRootLength("file://server") === 13 // "file://server"
* getRootLength("file://server/path") === 14 // "file://server/"
* getRootLength("http://server") === 13 // "http://server"
* getRootLength("http://server/path") === 14 // "http://server/"
* ```
*
* @internal
*/
export function getRootLength(path: string) {
const rootLength = getEncodedRootLength(path);
return rootLength < 0 ? ~rootLength : rootLength;
}
/**
* Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
* If the root is part of a URL, the twos-complement of the root length is returned.
*/
function getEncodedRootLength(path: string): number {
if (!path) return 0;
const ch0 = path.charCodeAt(0);
// POSIX or UNC
if (ch0 === CharacterCodes.slash || ch0 === CharacterCodes.backslash) {
if (path.charCodeAt(1) !== ch0) return 1; // POSIX: "/" (or non-normalized "\")
const p1 = path.indexOf(ch0 === CharacterCodes.slash ? directorySeparator : altDirectorySeparator, 2);
if (p1 < 0) return path.length; // UNC: "//server" or "\\server"
return p1 + 1; // UNC: "//server/" or "\\server\"
}
// DOS
if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodes.colon) {
const ch2 = path.charCodeAt(2);
if (ch2 === CharacterCodes.slash || ch2 === CharacterCodes.backslash) return 3; // DOS: "c:/" or "c:\"
if (path.length === 2) return 2; // DOS: "c:" (but not "c:d")
}
// URL
const schemeEnd = path.indexOf(urlSchemeSeparator);
if (schemeEnd !== -1) {
const authorityStart = schemeEnd + urlSchemeSeparator.length;
const authorityEnd = path.indexOf(directorySeparator, authorityStart);
if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path"
// For local "file" URLs, include the leading DOS volume (if present).
// Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a
// special case interpreted as "the machine from which the URL is being interpreted".
const scheme = path.slice(0, schemeEnd);
const authority = path.slice(authorityStart, authorityEnd);
if (scheme === "file" && (authority === "" || authority === "localhost") &&
isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) {
const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2);
if (volumeSeparatorEnd !== -1) {
if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodes.slash) {
// URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/"
return ~(volumeSeparatorEnd + 1);
}
if (volumeSeparatorEnd === path.length) {
// URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a"
// but not "file:///c:d" or "file:///c%3ad"
return ~volumeSeparatorEnd;
}
}
}
return ~(authorityEnd + 1); // URL: "file://server/", "http://server/"
}
return ~path.length; // URL: "file://server", "http://server"
}
// relative
return 0;
}
function isVolumeCharacter(charCode: number) {
return (charCode >= CharacterCodes.a && charCode <= CharacterCodes.z) ||
(charCode >= CharacterCodes.A && charCode <= CharacterCodes.Z);
}
function getFileUrlVolumeSeparatorEnd(url: string, start: number) {
const ch0 = url.charCodeAt(start);
if (ch0 === CharacterCodes.colon) return start + 1;
if (ch0 === CharacterCodes.percent && url.charCodeAt(start + 1) === CharacterCodes._3) {
const ch2 = url.charCodeAt(start + 2);
if (ch2 === CharacterCodes.a || ch2 === CharacterCodes.A) return start + 3;
}
return -1;
}
export const dirname = getDirectoryPath;
/**
* Removes a trailing directory separator from a path, if it does not already have one.
*
* ```ts
* removeTrailingDirectorySeparator("/path/to/file.ext") === "/path/to/file.ext"
* removeTrailingDirectorySeparator("/path/to/file.ext/") === "/path/to/file.ext"
* ```
*
* @internal
*/
export function removeTrailingDirectorySeparator(path: Path): Path;
/** @internal */
export function removeTrailingDirectorySeparator(path: string): string;
/** @internal */
export function removeTrailingDirectorySeparator(path: string) {
if (hasTrailingDirectorySeparator(path)) {
return path.substr(0, path.length - 1);
}
return path;
}
export const format = getPathFromPathComponents;
export const resolve = resolvePath;
export const compareCaseSensitive = comparePathsCaseSensitive;
export const compareCaseInsensitive = comparePathsCaseInsensitive;
export const isAbsolute = isRootedDiskPath;
export const isRoot = isDiskPathRoot;
export const parse = getPathComponents;
export const hasTrailingSeparator = hasTrailingDirectorySeparator;
export const reduce = reducePathComponents;
export const addTrailingSeparator = ensureTrailingDirectorySeparator;
export const compare = comparePaths;
export const relative = getRelativePathFromDirectory;
export const changeExtension = changeAnyExtension;
export const enum ValidationFlags {
None = 0,
RequireRoot = 1 << 0,
RequireDirname = 1 << 1,
RequireBasename = 1 << 2,
RequireExtname = 1 << 3,
RequireTrailingSeparator = 1 << 4,
AllowRoot = 1 << 5,
AllowDirname = 1 << 6,
AllowBasename = 1 << 7,
AllowExtname = 1 << 8,
AllowTrailingSeparator = 1 << 9,
AllowNavigation = 1 << 10,
AllowWildcard = 1 << 11,
/** Path must be a valid directory root */
Root = RequireRoot | AllowRoot | AllowTrailingSeparator,
/** Path must be a absolute */
Absolute = RequireRoot | AllowRoot | AllowDirname | AllowBasename | AllowExtname | AllowTrailingSeparator | AllowNavigation,
/** Path may be relative or absolute */
RelativeOrAbsolute = AllowRoot | AllowDirname | AllowBasename | AllowExtname | AllowTrailingSeparator | AllowNavigation,
/** Path may only be a filename */
Basename = RequireBasename | AllowExtname,
}
export function validate(path: string, flags: ValidationFlags = ValidationFlags.RelativeOrAbsolute) {
const components = parse(path);
const trailing = hasTrailingSeparator(path);
if (!validateComponents(components, flags, trailing)) throw vfs.createIOError("ENOENT");
return components.length > 1 && trailing ? format(reduce(components)) + sep : format(reduce(components));
}
const invalidRootComponentRegExp = /^(?!(\/|\/\/\w+\/|[a-zA-Z]:\/?|)$)/;
const invalidNavigableComponentRegExp = /[:*?"<>|]/;
const invalidNavigableComponentWithWildcardsRegExp = /[:"<>|]/;
const invalidNonNavigableComponentRegExp = /^\.{1,2}$|[:*?"<>|]/;
const invalidNonNavigableComponentWithWildcardsRegExp = /^\.{1,2}$|[:"<>|]/;
const extRegExp = /\.\w+$/;
function validateComponents(components: string[], flags: ValidationFlags, hasTrailingSeparator: boolean) {
const hasRoot = !!components[0];
const hasDirname = components.length > 2;
const hasBasename = components.length > 1;
const hasExtname = hasBasename && extRegExp.test(components[components.length - 1]);
const invalidComponentRegExp = flags & ValidationFlags.AllowNavigation
? flags & ValidationFlags.AllowWildcard ? invalidNavigableComponentWithWildcardsRegExp : invalidNavigableComponentRegExp
: flags & ValidationFlags.AllowWildcard ? invalidNonNavigableComponentWithWildcardsRegExp : invalidNonNavigableComponentRegExp;
// Validate required components
if (flags & ValidationFlags.RequireRoot && !hasRoot) return false;
if (flags & ValidationFlags.RequireDirname && !hasDirname) return false;
if (flags & ValidationFlags.RequireBasename && !hasBasename) return false;
if (flags & ValidationFlags.RequireExtname && !hasExtname) return false;
if (flags & ValidationFlags.RequireTrailingSeparator && !hasTrailingSeparator) return false;
// Required components indicate allowed components
if (flags & ValidationFlags.RequireRoot) flags |= ValidationFlags.AllowRoot;
if (flags & ValidationFlags.RequireDirname) flags |= ValidationFlags.AllowDirname;
if (flags & ValidationFlags.RequireBasename) flags |= ValidationFlags.AllowBasename;
if (flags & ValidationFlags.RequireExtname) flags |= ValidationFlags.AllowExtname;
if (flags & ValidationFlags.RequireTrailingSeparator) flags |= ValidationFlags.AllowTrailingSeparator;
// Validate disallowed components
if (~flags & ValidationFlags.AllowRoot && hasRoot) return false;
if (~flags & ValidationFlags.AllowDirname && hasDirname) return false;
if (~flags & ValidationFlags.AllowBasename && hasBasename) return false;
if (~flags & ValidationFlags.AllowExtname && hasExtname) return false;
if (~flags & ValidationFlags.AllowTrailingSeparator && hasTrailingSeparator) return false;
// Validate component strings
if (invalidRootComponentRegExp.test(components[0])) return false;
for (let i = 1; i < components.length; i++) {
if (invalidComponentRegExp.test(components[i])) return false;
}
return true;
}
export function isDeclaration(path: string) {
return isDeclarationFileName(path);
}
export const isTypeScript = hasTSFileExtension;
export const isJavaScript = hasJSFileExtension;
export const extname = getAnyExtensionFromPath;
export const basename = getBaseFileName;
export function isSourceMap(path: string) {
return extname(path, ".map", /*ignoreCase*/ false).length > 0;
}
export function isTsConfigFile(path: string): boolean {
return path.indexOf("tsconfig") !== -1 && path.indexOf("json") !== -1;
}

View File

@@ -0,0 +1,93 @@
import * as path from 'path'
import * as ts from 'typescript'
import { compileFiles, TestFile, Utils } from "./tsc-infrastructure/compiler-run";
import * as TestCaseParser from "./tsc-infrastructure/test-file-parser";
import * as fsp from 'fs/promises'
import { getDeclarationExtension, isDeclarationFile, isJavaScriptFile, isJSONFile, isSourceMapFile } from '../compiler/path-utils';
import { changeExtension } from './tsc-infrastructure/vpath';
import * as vpath from "./tsc-infrastructure/vpath";
import { libs } from './tsc-infrastructure/options';
import { ModuleKind } from 'typescript';
import { transformFile } from '../compiler/transform-file';
export function swapLocation(file: string, changed: string, extension: string | undefined = ".d.ts") {
const parentDir = path.dirname(path.dirname(file));
let baseFile = path.basename(file)
if (extension) {
const existingExtension = path.extname(file);
baseFile = baseFile.substring(0, baseFile.length - existingExtension.length) + extension;
}
return path.join(parentDir, changed, baseFile);
}
export interface FileContent {
content: string,
fileName: string
}
export async function loadTestCase(fileName: string) {
const rawText = await fsp.readFile(fileName, { encoding: "utf-8" });
const test = {
content: Utils.removeByteOrderMark(rawText),
file: fileName,
}
return Object.assign(TestCaseParser.makeUnitsFromTest(test.content, test.file), {
BOM: rawText.substring(0, Utils.getByteOrderMarkLength(rawText))
});
}
export function runTypeScript(caseData: TestCaseParser.TestCaseContent, settings: ts.CompilerOptions): FileContent[] {
function createHarnessTestFile(lastUnit: TestCaseParser.TestUnitData): TestFile {
return { unitName: lastUnit.name, content: lastUnit.content, fileOptions: lastUnit.fileOptions };
}
const toBeCompiled = caseData.testUnitData.map(unit => {
return createHarnessTestFile(unit);
});
const result = compileFiles(toBeCompiled, [], {
declaration: "true",
isolatedDeclarations: "true",
removeComments: "false",
}, settings, undefined);
return caseData.testUnitData
.filter(isRelevantTestFile)
.map(file => {
const declarationFile = changeExtension(file.name, getDeclarationExtension(file.name));
const resolvedDeclarationFile = vpath.resolve(result.vfs.cwd(), declarationFile);
const declaration = result.dts.get(resolvedDeclarationFile)
return {
content: declaration?.text ?? "",
fileName: declarationFile,
};
})
}
export function isRelevantTestFile(f: TestCaseParser.TestUnitData) {
return !isDeclarationFile(f.name) && !isJavaScriptFile(f.name) && !isJSONFile(f.name) && !isSourceMapFile(f.name) && f.content !== undefined
}
export function runIsolated(caseData: TestCaseParser.TestCaseContent, libFiles: string[], settings: ts.CompilerOptions): FileContent[] {
const toSrc = (n: string) => vpath.combine('/src', n);
const projectFiles = [...caseData.testUnitData.map(o => toSrc(o.name)), ...libFiles];
const packageJson = caseData.testUnitData.find(f => f.name === "/package.json");
let packageResolution: ts.ResolutionMode = ts.ModuleKind.CommonJS
if (packageJson) {
packageResolution = JSON.parse(packageJson.content)?.type === "module" ? ModuleKind.ESNext : ModuleKind.CommonJS
}
const results = caseData.testUnitData
.filter(isRelevantTestFile)
.map(file => {
const declaration = transformFile(toSrc(file.name), Utils.removeByteOrderMark(file.content), projectFiles, libs, settings, packageResolution)
return {
content: declaration.code,
fileName: changeExtension(file.name, getDeclarationExtension(file.name)),
};
})
return results;
}

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"pretty": true,
"lib": ["es2018", "DOM"],
"target": "es2018",
"module": "CommonJS",
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"composite": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictPropertyInitialization": true,
"skipLibCheck": true,
"alwaysStrict": true,
"preserveConstEnums": true,
"outDir": "../build"
}
}

View File

@@ -0,0 +1,140 @@
export type ArgTypeParser<T> = (name: string, value: string | undefined, existingValue: T | undefined) => T
function mustNotExist<T>(fn: ArgTypeParser<T>): ArgTypeParser<T> {
return (name, value, existingValue) => {
if (existingValue) {
throw new Error(`Parameter ${name} was specified multiple times. Values ${existingValue}, ${value}`);
}
return fn(name, value, existingValue);
}
}
export const ArgType = {
String: () => mustNotExist<string>((name, value) => {
if (value) {
return value;
}
throw new Error(`String value was not specified for ${name}`);
}),
Boolean: () => mustNotExist<boolean>((name, value) => {
if (value === undefined) {
return true;
}
if (value.toLowerCase() === "false") {
return false;
}
if (value.toLowerCase() === "true") {
return true;
}
throw new Error(`Invalid Boolean Value ${value} for ${name}`);
}),
Enum: <T extends string>(...values: T[]) => mustNotExist<T>((name, value,) => {
if (values.includes(value as T)) {
return value as T;
}
throw new Error(`Invalid Enum value, Expected one of ${values.join(",")}`);
}),
Number: () => mustNotExist<number>((name, value) => {
if (value && !Number.isNaN(+value)) {
return +value;
}
throw new Error(`Invalid Number value, found ${value}`);
}),
StringArray: () => (name, value, existingValue: string[] | undefined) => {
existingValue ??= [];
if (value) {
existingValue.push(value)
return existingValue;
}
throw new Error(`String value was not specified for ${name}`);
},
} satisfies Record<string, (...a: any[]) => ArgTypeParser<any>>
type ParserConfiguration<V> = Record<string, ArgTypeParser<any> | {
type: ArgTypeParser<any>,
required?: V,
description: string,
}>
type ParsedValue<T extends ParserConfiguration<boolean>> = {
[P in keyof T]:
T[P] extends ArgTypeParser<infer A> ? A | undefined :
T[P] extends {
type: ArgTypeParser<infer A>,
required?: infer R
} ? R extends true ? A : A | undefined : never
}
export function parserConfiguration<V extends boolean, T extends ParserConfiguration<V>>(config:T) {
return config;
}
export function parseArgs<V extends boolean, T extends ParserConfiguration<V>>(args: string[], types: T): {
value: ParsedValue<T>,
diagnostics: string[],
usage: () => string
printUsageOnErrors: () => void;
} {
const config: Record<string, any> = {}
const diagnostics: string[] = []
function parseArgument(name: string, value: string | undefined) {
const existingValue = config[name]
const parser = types[name];
if(!parser) {
diagnostics.push(`Parameter ${name} was unexpected`)
return;
}
const parserFn = typeof parser === "function" ? parser: parser.type;
try {
const newValue = parserFn(name, value, existingValue);
config[name] = newValue;
} catch(e) {
if(e instanceof Error) {
diagnostics.push(e.message);
}
throw e;
}
}
for (const arg of args) {
const named = /--(?<name>.*)=(?<value>.*)/.exec(arg);
if (named) {
parseArgument(named.groups?.name!, named.groups?.value);
}
else {
const flagParam =/--(?<name>.*)/.exec(arg);
if (flagParam) {
parseArgument(flagParam.groups?.name!, undefined);
} else {
parseArgument('default', arg);
}
}
}
for(const key of Object.keys(types)) {
const cfg = types[key];
if(!(key in config) && 'required' in cfg && cfg.required) {
diagnostics.push(`Parameters ${key} is required`);
}
}
function usage() {
return Object.entries(types)
.map(([name, v]) => ({
name,
...(typeof v === "object" ? v: { })
}))
.filter(o => !!o.description)
.map(({ name, description, required }) => `--${name} \t ${description} \t ${required? "required": ""}`)
.join("\n");
}
return {
value: config as any,
diagnostics,
usage,
printUsageOnErrors() {
if(diagnostics.length) {
diagnostics.forEach(s => console.log(s));
console.log(usage());
process.exit();
}
},
}
}

View File

@@ -0,0 +1,119 @@
import * as fs from 'fs'
import * as fsp from 'fs/promises'
import { flatten, stableSort, compareStringsCaseSensitive } from '../compiler/lang-utils';
import { normalizePath, createGetCanonicalFileName, combinePaths } from '../compiler/path-utils';
import { FileSystemEntries } from '../test-runner/tsc-infrastructure/vfs';
const cache: Record<string, true> = {}
export async function ensureDir(dirName: string) {
const exists = cache[dirName] ??
(await fsp.access(dirName).then(() => true, () => false));
if (!exists) {
await fsp.mkdir(dirName, { recursive: true });
cache[dirName] = true;
}
}
let writeQueue = [0, 0, 0, 0, 0].map(() => Promise.resolve());
let writeQueueIndex = 0;
export function addToQueue(fn: () => Promise<void>) {
const index = writeQueueIndex++ % writeQueue.length;
writeQueue[index] = writeQueue[index].then(() => {
return fn();
});
}
export function flushQueue() {
return Promise.all(writeQueue);
}
/**
* @param path directory of the tsconfig.json
*
* @internal
*/
export function readAllFiles(path: string, regex: RegExp): string[] {
path = normalizePath(path);
// Associate an array of results with each include regex. This keeps results in order of the "include" order.
// If there are no "includes", then just put everything in results[0].
const results: string[] = [];
const visited = new Map<string, true>();
const toCanonical = createGetCanonicalFileName(false);
visitDirectory(path, path);
return flatten(results);
function visitDirectory(path: string, absolutePath: string) {
const canonicalPath = toCanonical(absolutePath);
if (visited.has(canonicalPath)) return;
visited.set(canonicalPath, true);
const { files, directories } = getAccessibleFileSystemEntries(path);
for (const current of stableSort<string>(files, compareStringsCaseSensitive)) {
const name = combinePaths(path, current);
if (!regex.exec(name)) continue;
const absoluteName = combinePaths(absolutePath, current);
results.push(absoluteName);
}
for (const current of stableSort<string>(directories, compareStringsCaseSensitive)) {
const name = combinePaths(path, current);
const absoluteName = combinePaths(absolutePath, current);
visitDirectory(name, absoluteName);
}
}
}
function getAccessibleFileSystemEntries(path: string): FileSystemEntries {
try {
const entries = fs.readdirSync(path || ".", { withFileTypes: true });
const files: string[] = [];
const directories: string[] = [];
for (const dirent of entries) {
// withFileTypes is not supported before Node 10.10.
const entry = typeof dirent === "string" ? dirent : dirent.name;
// This is necessary because on some file system node fails to exclude
// "." and "..". See https://github.com/nodejs/node/issues/4002
if (entry === "." || entry === "..") {
continue;
}
let stat: any;
if (typeof dirent === "string" || dirent.isSymbolicLink()) {
const name = combinePaths(path, entry);
try {
stat = fs.statSync(name);
if (!stat) {
continue;
}
}
catch (e) {
continue;
}
}
else {
stat = dirent;
}
if (stat.isFile()) {
files.push(entry);
}
else if (stat.isDirectory()) {
directories.push(entry);
}
}
files.sort();
directories.sort();
return { files, directories };
}
catch (e) {
return { files: [], directories: [] };
}
}

View File

@@ -0,0 +1,8 @@
export declare class Person {
age: number;
greet(): void;
overloaded(a: number): number;
overloaded(a: string): string;
get ageAccessor(): number;
set ageAccessor(value: number);
}

View File

@@ -0,0 +1 @@
export declare const test = 1;

View File

@@ -0,0 +1 @@
export declare const test: number;

View File

@@ -0,0 +1,2 @@
declare const value: number;
export default value;

View File

@@ -0,0 +1,8 @@
declare function test(a: string): number;
export declare const testAlias: typeof test;
export declare function testOptional(a: string, b?: string): number;
export declare function testDefault(a: string, b?: string): number;
export declare function testRest(...a: string[]): number;
export declare function testTuple(...a: [string, string]): number;
export declare function testTupleRest(...a: [string, string] | [number, number]): number;
export {};

View File

@@ -0,0 +1,10 @@
declare class Cell {
}
declare class Ship {
isSunk: boolean;
}
declare class Board {
ships: Ship[];
cells: Cell[];
private allShipsSunk;
}

View File

@@ -0,0 +1,7 @@
import { Person } from './class-all';
import * as fns from './functions';
import def from './export-default';
export declare let person: Person;
export declare const nr: number;
export declare const fff: typeof fns;
export default def;

View File

@@ -0,0 +1,6 @@
export interface I {
n: number;
o: {
s: string;
};
}

View File

@@ -0,0 +1 @@
export declare let test: number;

View File

@@ -0,0 +1,5 @@
import { Person } from "./class-all";
type Q = P;
type P = Person;
export type D = Q;
export {};

View File

@@ -0,0 +1,5 @@
{
"compilerOptions": {
"strict": true
}
}

View File

@@ -0,0 +1,8 @@
type PersonType = {
age: string;
};
type Id<T> = {
[P in keyof T]: T[P];
};
export type P = Id<PersonType>;
export {};

View File

@@ -0,0 +1 @@
*.js

View File

@@ -0,0 +1,36 @@
type TA = string;
type UA = string;
export type Conditional<UA> = UA extends infer TA ? TA: never;
type TF = string;
type UF = string;
export function test<UF>(o: UF extends infer TF ? TF: never): UF extends infer TF ? TF: never {
return null!
}
type TC = string;
type UC = string;
export class C<UC> {
member!: UC extends infer TC ? TC: never
get accessor(): UC extends infer TC ? TC: never {
return null!
}
set accessor(value: UC extends infer TC ? TC: never) {
}
constructor(p: UC extends infer TC ? TC: never) {
return null!;
}
method(p: UC extends infer TC ? TC: never): UC extends infer TC ? TC: never {
return null!;
}
}
type TI = string;
type UI = string;
export interface I<UI> {
member: UI extends infer TI ? TI: never
method(p: UI extends infer TI ? TI: never): UI extends infer TI ? TI: never;
new (p: UI extends infer TI ? TI: never): UI extends infer TI ? TI: never;
}

View File

@@ -0,0 +1,5 @@
export default class Class {
parent!: Class;
}
export const instance: Class = new Class();

View File

@@ -0,0 +1,5 @@
export default function foo(): typeof foo {
return foo;
}
export const instance: typeof foo = foo;

View File

@@ -0,0 +1,5 @@
const X = 1;
export const enum E {
X = 1,
Y = X
}

View File

@@ -0,0 +1,5 @@
export interface I {
}
const I = 1;
export const O: I = {}

View File

@@ -0,0 +1,4 @@
const p: string ="";
export function test<UF>(p: number, r: typeof p): UF extends infer TF ? TF: typeof p {
return null!
}

View File

@@ -0,0 +1,8 @@
// x0.d.ts
export declare let a: invalid;
// x.d.ts
declare module "./observable" {
// import { a } from "./x0";
export { a } from "./x0";
}

View File

@@ -0,0 +1,19 @@
export class Person {
age: number = 1;
greet(): void {
console.log("Hi")
}
overloaded(a: number): number;
overloaded(a: string): string;
overloaded(a: any): any{
return a;
}
get ageAccessor(): number {
return this.age;
}
set ageAccessor(value: number) {
this.age = value;
}
}

View File

@@ -0,0 +1 @@
export const test = 1;

View File

@@ -0,0 +1 @@
export const test: number = 0;

View File

@@ -0,0 +1,2 @@
const value: number = 10;
export default value

View File

@@ -0,0 +1,29 @@
function test(a: string): number {
return 0;
}
export const testAlias: typeof test = test;
export function testOptional(a: string, b?: string): number {
return 0;
}
export function testDefault(a: string, b: string = ""): number {
return 0;
}
export function testRest(...a: string[]): number {
return 0;
}
export function testTuple(...a: [string, string]): number {
return 0;
}
export function testTupleRest(...a:
| [string, string]
| [number, number]): number {
return 0;
}

View File

@@ -0,0 +1,15 @@
class Cell {
}
class Ship {
isSunk: boolean;
}
class Board {
ships: Ship[];
cells: Cell[];
private allShipsSunk() {
return this.ships.every(function (val) { return val.isSunk; });
}
}

View File

@@ -0,0 +1,12 @@
import { Person } from './class-all';
import * as fns from './functions';
import * as fns2 from './functions';
import def from './export-default';
export let person: Person = new Person;
export const nr: number = fns2.testAlias("1");
export const fff: typeof fns = fns;
export default def;

View File

@@ -0,0 +1,6 @@
export interface I {
n: number;
o: {
s: string
}
}

View File

@@ -0,0 +1 @@
export let test: number = 0;

View File

@@ -0,0 +1,4 @@
class Test {
#foo = 1
#bar: typeof this.#foo = 1;
}

View File

@@ -0,0 +1,4 @@
import { Person } from "./class-all";
type Q = P;
type P = Person;
export type D = Q;

View File

@@ -0,0 +1,6 @@
{
"compilerOptions": {
"strict": true,
"target": "ESNext"
}
}

View File

@@ -0,0 +1,13 @@
type PersonType = {
age: string;
}
type Id<T> = {
[P in keyof T]: T[P]
}
export type P = Id<PersonType>
function x() {
return 1;
}

View File

@@ -0,0 +1,3 @@
// @target: es2015
// @filename: extendedEscapesForAstralsInVarsAndClasses.ts
export var _𐊧 = ""

View File

@@ -19,8 +19,8 @@
"type": "git",
"url": "https://github.com/Microsoft/TypeScript.git"
},
"main": "./lib/typescript.js",
"typings": "./lib/typescript.d.ts",
"main": "./built/local/typescript.js",
"typings": "./built/local/typescript.d.ts",
"bin": {
"tsc": "./bin/tsc",
"tsserver": "./bin/tsserver"