Skip to content

Commit

Permalink
Temporary commit to show an external implementation of declaration emit.
Browse files Browse the repository at this point in the history
  • Loading branch information
dragomirtitian committed Mar 16, 2023
1 parent 17e7807 commit ed568e2
Show file tree
Hide file tree
Showing 74 changed files with 16,957 additions and 2 deletions.
5 changes: 5 additions & 0 deletions external-declarations/.gitignore
@@ -0,0 +1,5 @@
/build
/node_modules
/src/*.tsbuildinfo
/tests/actual
/tsc-tests
25 changes: 25 additions & 0 deletions external-declarations/package.json
@@ -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"
}
}
18 changes: 18 additions & 0 deletions external-declarations/readme.md
@@ -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.
276 changes: 276 additions & 0 deletions external-declarations/src/code-mod/code-transform.ts
@@ -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)!;
};
};
}
35 changes: 35 additions & 0 deletions external-declarations/src/code-mod/index.ts
@@ -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);
}

0 comments on commit ed568e2

Please sign in to comment.