Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added support for TS 5.5 #154

Merged
merged 3 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ jobs:

strategy:
matrix:
node-version: [ 18.x, 20.x, 22.x ]
# NOTE - 22 is failing due to odd bug - maybe bug in node? Need to re-enable later
node-version: [ 18.x, 20.x ]

steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Setup Node.js 19.x to publish to npmjs.org
uses: actions/setup-node@v1
with:
node-version: '19.x'
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'

- name: Install Packages
Expand Down
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,16 @@
"ts-node": "^10.9.1",
"ts-patch": "3.0.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.3.2"
"typescript": "^5.4.5",
"ts-next": "npm:typescript@beta",
"ts-expose-internals" : "npm:[email protected]"
},
"workspaces": {
"nohoist": [
"jest",
"ts-jest",
"typescript"
]
},
"directories": {
"resources": "./dist/resources"
Expand Down
4 changes: 0 additions & 4 deletions projects/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ export const corePatchName = `<core>`;
export const modulePatchFilePath = path.resolve(appRoot, tspPackageJSON.directories.resources, 'module-patch.js');
export const dtsPatchFilePath = path.resolve(appRoot, tspPackageJSON.directories.resources, 'module-patch.d.ts');

// TODO - should do this in a better/dynamic way later
export const tsWrapperOpen = `var ts = (() => {`;
export const tsWrapperClose = `})();`;

export const execTscCmd = 'execTsc';

// endregion
Expand Down
9 changes: 7 additions & 2 deletions projects/core/src/module/module-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export interface ModuleSource {
fileFooter?: SourceSection;
usesTsNamespace: boolean;
getSections(): [ sectionName: SourceSection['sectionName'], section: SourceSection | undefined ][];
bodyWrapper?: {
start: string;
end: string;
}
}

// endregion
Expand All @@ -26,7 +30,7 @@ export interface ModuleSource {
export function getModuleSource(tsModule: TsModule): ModuleSource {
const moduleFile = tsModule.getUnpatchedModuleFile();

const { firstSourceFileStart, fileEnd, wrapperPos, bodyPos, sourceFileStarts } =
const { firstSourceFileStart, fileEnd, wrapperPos, bodyPos, sourceFileStarts, bodyWrapper } =
sliceModule(moduleFile, tsModule.package.version);

const fileHeaderEnd = wrapperPos?.start ?? firstSourceFileStart;
Expand All @@ -47,7 +51,8 @@ export function getModuleSource(tsModule: TsModule): ModuleSource {
...this.body.map((section, i) => [ `body`, section ] as [ SourceSection['sectionName'], SourceSection ]),
[ 'file-footer', this.fileFooter ],
];
}
},
bodyWrapper,
}
}

Expand Down
44 changes: 29 additions & 15 deletions projects/core/src/patch/patch-module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import ts from 'typescript';
import fs from 'fs';
import {
defaultNodePrinterOptions, dtsPatchFilePath, execTscCmd, modulePatchFilePath, tsWrapperClose, tsWrapperOpen
} from '../config';
import { defaultNodePrinterOptions, dtsPatchFilePath, execTscCmd, modulePatchFilePath } from '../config';
import { getTsModule, TsModule } from '../module';
import {
addOriginalCreateProgramTransformer, createMergeStatementsTransformer, fixTsEarlyReturnTransformer,
hookTscExecTransformer, patchCreateProgramTransformer, patchEmitterTransformer
addOriginalCreateProgramTransformer, createMergeStatementsTransformer, createProgramExportFiles,
fixTsEarlyReturnTransformer, hookTscExecTransformer, patchCreateProgramTransformer, patchEmitterTransformer
} from './transformers';
import { SourceSection } from '../module/source-section';
import { PatchError } from '../system';
Expand Down Expand Up @@ -39,6 +37,7 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js:
}

const source = tsModule.getUnpatchedSource();
const { bodyWrapper } = source;

const printableBodyFooters: (SourceSection | string)[] = [];
const printableFooters: (SourceSection | string)[] = [];
Expand All @@ -64,11 +63,13 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js:
source.body.unshift(...tsSource.body);

/* Fix early return */
const typescriptSection = source.body.find(s => s.srcFileName === 'src/typescript/typescript.ts');
if (!typescriptSection) throw new PatchError(`Could not find Typescript source section`);
typescriptSection.transform([ fixTsEarlyReturnTransformer ]);

printableBodyFooters.push(`return returnResult;`);
// NOTE - This exists up until TS 5.4, but isn't there for 5.5+
if (tsModule.majorVer <= 5 && tsModule.minorVer <= 4) {
const typescriptSection = source.body.find(s => s.srcFileName === 'src/typescript/typescript.ts');
if (!typescriptSection) throw new PatchError(`Could not find Typescript source section`);
typescriptSection.transform([ fixTsEarlyReturnTransformer ]);
printableBodyFooters.push(`return returnResult;`);
}
}

/* Patch Program */
Expand All @@ -77,9 +78,22 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js:
programSection.transform([ patchCreateProgramTransformer ]);

/* Add originalCreateProgram to exports */
const namespacesTsSection = source.body.find(s => s.srcFileName === 'src/typescript/_namespaces/ts.ts');
if (!namespacesTsSection) throw new PatchError(`Could not find NamespacesTs source section`);
namespacesTsSection.transform([ addOriginalCreateProgramTransformer ]);
let createProgramAdded = false;
for (const fileName of createProgramExportFiles) {
// As of TS 5.5, we have to handle cases of multiple instances of the same file name. In this case, we need to
// handle both src/typescript/typescript.ts
const sections = source.body.filter(s => s.srcFileName === fileName);
for (const section of sections) {
try {
section.transform([ addOriginalCreateProgramTransformer ]);
createProgramAdded = true;
} catch (e) {
if (!(e instanceof PatchError)) throw e;
}
}
}

if (!createProgramAdded) throw new PatchError(`Could not find any of the createProgram export files`);

/* Patch emitter (for diagnostics tools) */
const emitterSection = source.body.find(s => s.srcFileName === 'src/compiler/watch.ts');
Expand Down Expand Up @@ -128,7 +142,7 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js:

/* Body Wrapper Open */
if (shouldWrap) {
list.push([ `\n${tsWrapperOpen}\n`, indentLevel ]);
if (bodyWrapper) list.push([ `\n${bodyWrapper.start}\n`, indentLevel ]);
indentLevel = 2;
}

Expand All @@ -144,7 +158,7 @@ export function patchModule(tsModule: TsModule, skipDts: boolean = false): { js:
/* Body Wrapper Close */
if (shouldWrap) {
indentLevel = 0;
list.push([ `\n${tsWrapperClose}\n`, indentLevel ]);
if (bodyWrapper) list.push([ `\n${bodyWrapper.end}\n`, indentLevel ]);
}

/* File Footer */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
import ts from 'typescript';
import { PatchError } from '../../system';


/* ****************************************************************************************************************** */
// region: Config
/* ****************************************************************************************************************** */

export const createProgramExportFiles = [
/* TS < 5.4 */
'src/typescript/_namespaces/ts.ts',

/* TS >= 5.4 */
'src/server/_namespaces/ts.ts',
'src/typescript/typescript.ts'
]

// endregion


/* ****************************************************************************************************************** */
Expand All @@ -11,38 +28,39 @@ export function addOriginalCreateProgramTransformer(context: ts.TransformationCo
let patchSuccess = false;

return (sourceFile: ts.SourceFile) => {
if (sourceFile.fileName !== 'src/typescript/_namespaces/ts.ts')
if (!createProgramExportFiles.includes(sourceFile.fileName))
throw new Error('Wrong emitter file sent to transformer! This should be unreachable.');

const res = factory.updateSourceFile(sourceFile, ts.visitNodes(sourceFile.statements, visitNodes) as unknown as ts.Statement[]);

if (!patchSuccess) throw new Error('Failed to patch typescript early return!');
if (!patchSuccess) throw new PatchError('Failed to patch typescript originalCreateProgram!');

return res;

function visitNodes(node: ts.Statement): ts.VisitResult<ts.Node> {
/* Handle: __export({ ... }) */
if (
ts.isExpressionStatement(node) &&
ts.isCallExpression(node.expression) &&
node.expression.expression.getText() === "__export"
node.expression.expression.getText() === '__export'
) {
const exportObjectLiteral = node.expression.arguments[1];
if (ts.isObjectLiteralExpression(exportObjectLiteral)) {
const originalCreateProgramProperty = factory.createPropertyAssignment(
"originalCreateProgram",
'originalCreateProgram',
factory.createArrowFunction(
undefined,
undefined,
[],
undefined,
factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
factory.createIdentifier("originalCreateProgram")
factory.createIdentifier('originalCreateProgram')
)
);

const updatedExportObjectLiteral = factory.updateObjectLiteralExpression(
exportObjectLiteral,
[...exportObjectLiteral.properties, originalCreateProgramProperty]
[ ...exportObjectLiteral.properties, originalCreateProgramProperty ]
);

const updatedNode = factory.updateExpressionStatement(
Expand All @@ -51,7 +69,7 @@ export function addOriginalCreateProgramTransformer(context: ts.TransformationCo
node.expression,
node.expression.expression,
undefined,
[node.expression.arguments[0], updatedExportObjectLiteral]
[ node.expression.arguments[0], updatedExportObjectLiteral ]
)
);

Expand All @@ -60,6 +78,49 @@ export function addOriginalCreateProgramTransformer(context: ts.TransformationCo
}
}

/* Handle: 1 && (module.exports = { ... }) (ts5.5+) */
if (
ts.isExpressionStatement(node) && ts.isBinaryExpression(node.expression) &&
node.expression.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&
ts.isParenthesizedExpression(node.expression.right) &&
ts.isBinaryExpression(node.expression.right.expression) &&
node.expression.right.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
ts.isPropertyAccessExpression(node.expression.right.expression.left) &&
node.expression.right.expression.left.expression.getText() === 'module' &&
node.expression.right.expression.left.name.getText() === 'exports' &&
ts.isObjectLiteralExpression(node.expression.right.expression.right)
) {
// Add originalCreateProgram to the object literal
const originalCreateProgramProperty = factory.createShorthandPropertyAssignment('originalCreateProgram');

const updatedObjectLiteral = factory.updateObjectLiteralExpression(
node.expression.right.expression.right,
[ ...node.expression.right.expression.right.properties, originalCreateProgramProperty ]
);

// Update the node
const updatedNode = factory.updateExpressionStatement(
node,
factory.updateBinaryExpression(
node.expression,
node.expression.left,
node.expression.operatorToken,
factory.updateParenthesizedExpression(
node.expression.right,
factory.updateBinaryExpression(
node.expression.right.expression,
node.expression.right.expression.left,
node.expression.right.expression.operatorToken,
updatedObjectLiteral
)
)
)
);

patchSuccess = true;
return updatedNode;
}

return node;
}
};
Expand Down
19 changes: 15 additions & 4 deletions projects/core/src/slice/module-slice.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ModuleFile } from '../module';
import { Position } from '../system';
import semver from 'semver';
import { sliceTs5 } from './ts5';
import { sliceTs54 } from './ts54';
import { sliceTs55 } from './ts55';


/* ****************************************************************************************************************** */
Expand All @@ -15,6 +16,10 @@ export interface ModuleSlice {
bodyPos: Position
fileEnd: number
sourceFileStarts: [ name: string, position: number ][]
bodyWrapper?: {
start: string;
end: string;
}
}

// endregion
Expand All @@ -25,12 +30,18 @@ export interface ModuleSlice {
/* ****************************************************************************************************************** */

export function sliceModule(moduleFile: ModuleFile, tsVersion: string) {
if (semver.lte(tsVersion, '5.0.0')) {
const baseVersion = semver.coerce(tsVersion, { includePrerelease: false });
if (!baseVersion) throw new Error(`Could not parse TS version: ${tsVersion}`);

if (semver.lt(baseVersion, '5.0.0')) {
throw new Error(`Cannot patch TS version <5`);
}

/* Handle 5+ */
return sliceTs5(moduleFile);
if (semver.lt(baseVersion, '5.5.0')) {
return sliceTs54(moduleFile);
}

return sliceTs55(moduleFile);
}

/** @internal */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { ModuleSlice } from './module-slice';
// region: Utils
/* ****************************************************************************************************************** */

export function sliceTs5(moduleFile: ModuleFile): ModuleSlice {
/**
* Slice 5.0 - 5.4
*/
export function sliceTs54(moduleFile: ModuleFile): ModuleSlice {
let firstSourceFileStart: number;
let wrapperStart: number | undefined;
let wrapperEnd: number | undefined;
Expand Down Expand Up @@ -65,7 +68,11 @@ export function sliceTs5(moduleFile: ModuleFile): ModuleSlice {
wrapperPos: wrapperStart != null ? { start: wrapperStart, end: wrapperEnd! } : undefined,
fileEnd: content.length,
bodyPos: { start: bodyStart, end: bodyEnd },
sourceFileStarts
sourceFileStarts,
bodyWrapper: {
start: 'var ts = (() => {',
end: '})();'
}
};
}

Expand Down
Loading
Loading