Skip to content

Commit

Permalink
Add initial benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
kitten committed Nov 23, 2023
1 parent 1be32fb commit 0b46a49
Show file tree
Hide file tree
Showing 10 changed files with 906 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
]
},
"devDependencies": {
"@0no-co/typescript.js": "5.3.2-2",
"@changesets/cli": "^2.26.2",
"@changesets/get-github-info": "^0.5.2",
"@rollup/plugin-buble": "^1.0.2",
Expand Down
12 changes: 11 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions src/__tests__/parser.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, bench } from 'vitest';
import * as ts from './tsHarness';

describe('Document', () => {
const virtualHost = ts.createVirtualHost({
...ts.readVirtualModule('@0no-co/graphql.web'),
'parser.ts': ts.readFileFromRoot('src/parser.ts'),
'index.ts': `
import type { Document } from './parser';
type document = Document<'{ test }'>;
type operation = document['definitions'][0]['operation'];
`,
});

const typeHost = ts.createTypeHost(['index.ts'], virtualHost);

bench('parse document', () => {
ts.createTypeChecker(typeHost).getDiagnostics();
});
});
28 changes: 28 additions & 0 deletions src/__tests__/tsHarness/compilerOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
CompilerOptions,
ModuleResolutionKind,
ScriptTarget,
JsxEmit,
} from '@0no-co/typescript.js';

export const compilerOptions: CompilerOptions = {
rootDir: '/',
moduleResolution: ModuleResolutionKind.Bundler,
skipLibCheck: true,
skipDefaultLibCheck: true,
allowImportingTsExtensions: true,
allowSyntheticDefaultImports: true,
resolvePackageJsonExports: true,
resolvePackageJsonImports: true,
resolveJsonModule: true,
esModuleInterop: true,
jsx: 1 satisfies JsxEmit.Preserve,
target: 99 satisfies ScriptTarget.Latest,
checkJs: false,
allowJs: true,
strict: false,
noEmit: true,
noLib: false,
disableSizeLimit: true,
disableSolutionSearching: true,
};
7 changes: 7 additions & 0 deletions src/__tests__/tsHarness/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type { FileData, Files, VirtualHost } from './virtualHost';
export type { TypeHost } from './typeCheckerHost';

export { createTypeChecker } from '@0no-co/typescript.js';
export { createTypeHost } from './typeCheckerHost';
export { createVirtualHost } from './virtualHost';
export { readFileFromRoot, readVirtualModule } from './virtualModules';
261 changes: 261 additions & 0 deletions src/__tests__/tsHarness/resolution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import {
StringLiteralLike,
ModuleDeclaration,
ModifierFlags,
StringLiteral,
ModuleBlock,
Identifier,
SourceFile,
SyntaxKind,
Statement,
ModuleKind,
CompilerHost,
CompilerOptions,
ScriptTarget,
AssertClause,
Node,
toPath,
forEach,
forEachChild,
setResolvedModule,
getNormalizedAbsolutePath,
isExternalModuleNameRelative,
getTextOfIdentifierOrLiteral,
hasSyntacticModifier,
setParentRecursive,
getExternalModuleName,
isAnyImportOrReExport,
isStringLiteralLike,
isLiteralImportTypeNode,
isModuleDeclaration,
isExportDeclaration,
isImportDeclaration,
isImportEqualsDeclaration,
isImportTypeNode,
isExclusivelyTypeOnlyImportOrExport,
walkUpParenthesizedExpressions,
isExternalModule,
isAmbientModule,
isStringLiteral,
isSourceFileJS,
isRequireCall,
isImportCall,
hasJSDocNodes,
} from '@0no-co/typescript.js';

function _collectExternalModuleReferences(file: SourceFile) {
if (file.imports) {
return;
}

const isJavaScriptFile = isSourceFileJS(file);
const isExternalModuleFile = isExternalModule(file);
const imports: StringLiteralLike[] = [];
const moduleAugmentations: (StringLiteral | Identifier)[] = [];
const ambientModules: string[] = [];

for (const node of file.statements) {
_collectModuleReferences(node, false);
}

if (isJavaScriptFile) {
_collectDynamicImportOrRequireCalls(file);
}

file.imports = imports;
file.moduleAugmentations = moduleAugmentations;
file.ambientModuleNames = ambientModules;

function _collectModuleReferences(node: Statement, inAmbientModule: boolean) {
if (isAnyImportOrReExport(node)) {
const moduleNameExpr = getExternalModuleName(node);
if (moduleNameExpr && isStringLiteral(moduleNameExpr) && moduleNameExpr.text) {
setParentRecursive(node, /*incremental*/ false);
imports.push(moduleNameExpr);
}
} else if (
isModuleDeclaration(node) &&
isAmbientModule(node) &&
(inAmbientModule ||
hasSyntacticModifier(node, ModifierFlags.Ambient) ||
file.isDeclarationFile)
) {
const moduleName = getTextOfIdentifierOrLiteral(node.name);
if (isExternalModuleFile || (inAmbientModule && !isExternalModuleNameRelative(moduleName))) {
moduleAugmentations.push(node.name);
} else if (!inAmbientModule) {
if (file.isDeclarationFile) ambientModules.push(moduleName);
const body = (node as ModuleDeclaration).body as ModuleBlock;
for (const statement of body?.statements || []) {
_collectModuleReferences(statement, true);
}
}
}
}

function _collectDynamicImportOrRequireCalls(file: SourceFile) {
const r = /import|require/g;
while (r.exec(file.text) !== null) {
const node = getNodeAtPosition(file, r.lastIndex);
if (isJavaScriptFile && isRequireCall(node, true)) {
setParentRecursive(node, false);
imports.push(node.arguments[0]);
} else if (
isImportCall(node) &&
node.arguments.length >= 1 &&
isStringLiteralLike(node.arguments[0])
) {
setParentRecursive(node, false);
imports.push(node.arguments[0]);
} else if (isLiteralImportTypeNode(node)) {
setParentRecursive(node, false);
imports.push(node.argument.literal);
}
}
}

function getNodeAtPosition(sourceFile: SourceFile, position: number): Node {
let current: Node = sourceFile;
const getContainingChild = (child: Node) => {
if (
child.pos <= position &&
(position < child.end ||
(position === child.end && child.kind === SyntaxKind.EndOfFileToken))
) {
return child;
}
};
while (true) {
const child =
(isJavaScriptFile &&
hasJSDocNodes(current) &&
forEach(current.jsDoc, getContainingChild)) ||
forEachChild(current, getContainingChild);
if (!child) {
return current;
} else {
current = child;
}
}
}
}

function _getModuleResolutionName(literal: StringLiteralLike) {
return literal.text;
}

function _getModuleNames({ imports, moduleAugmentations }: SourceFile): StringLiteralLike[] {
const res = [...imports];
for (const aug of moduleAugmentations) if (aug.kind === SyntaxKind.StringLiteral) res.push(aug);
return res;
}

const emptyResolution = {
resolvedModule: undefined,
resolvedTypeReferenceDirective: undefined,
};

function getResolutionModeOverrideForClause(clause: AssertClause | undefined) {
if (!clause || clause.elements.length !== 1) return undefined;
const elem = clause.elements[0];
if (
!isStringLiteralLike(elem.name) ||
elem.name.text !== 'resolution-mode' ||
!isStringLiteralLike(elem.value)
) {
return undefined;
} else if (elem.value.text === 'import') {
return 99 as ModuleKind.ESNext;
} else if (elem.value.text === 'require') {
return 1 as ModuleKind.CommonJS;
}
}

function _getModeForUsageLocation(file: SourceFile, usage: StringLiteralLike) {
if (file.impliedNodeFormat === undefined) return undefined;

if (
(isImportDeclaration(usage.parent) || isExportDeclaration(usage.parent)) &&
isExclusivelyTypeOnlyImportOrExport(usage.parent)
) {
const override = getResolutionModeOverrideForClause(usage.parent.assertClause);
if (override) return override;
}

if (usage.parent.parent && isImportTypeNode(usage.parent.parent)) {
const override = getResolutionModeOverrideForClause(
usage.parent.parent.assertions?.assertClause
);
if (override) return override;
}

if (file.impliedNodeFormat !== (99 as ModuleKind.ESNext)) {
return isImportCall(walkUpParenthesizedExpressions(usage.parent))
? ModuleKind.ESNext
: ModuleKind.CommonJS;
} else {
const exprParentParent = walkUpParenthesizedExpressions(usage.parent)?.parent;
return exprParentParent && isImportEqualsDeclaration(exprParentParent)
? ModuleKind.CommonJS
: ModuleKind.ESNext;
}
}

export function* processImportedModules(
file: SourceFile | undefined,
host: CompilerHost,
options: CompilerOptions
): IterableIterator<SourceFile> {
if (!file) return;
_collectExternalModuleReferences(file);
if (!resolvedModules.has(file.path) && (file.imports.length || file.moduleAugmentations.length)) {
const moduleNames = _getModuleNames(file);
const resolutions = _resolveModuleNames(moduleNames, file);
if (resolutions.length === moduleNames.length) {
for (let index = 0; index < moduleNames.length; index++) {
const moduleName = moduleNames[index];
const resolvedModule = resolutions[index];
const resolution = resolvedModule ? { resolvedModule } : emptyResolution;
const mode = _getModeForUsageLocation(file, moduleNames[index]);
const resolutionsInFile = createModeAwareCache<ResolutionWithFailedLookupLocations>();
resolvedModules.set(file.path, resolutionsInFile);
if (resolvedModule) {
const file = findSourceFile(resolvedModule.resolvedFileName, host);
if (file) {
yield* processImportedModules(file, host, options);
yield file;
}
}
}
}
}

function _resolveModuleNames(
moduleNames: readonly StringLiteralLike[],
containingFile: SourceFile
) {
if (!moduleNames.length) return [];
const currentDirectory = host.getCurrentDirectory();
const containingFileName = getNormalizedAbsolutePath(
containingFile.originalFileName,
currentDirectory
);
return host.resolveModuleNames!(
moduleNames.map(_getModuleResolutionName),
containingFileName,
undefined,
undefined,
options
);
}
}

export function findSourceFile(fileName: string, host: CompilerHost): SourceFile | undefined {
const file = host.getSourceFile(fileName, 99 as ScriptTarget.ESNext);
if (file) {
const path = toPath(fileName, host.getCurrentDirectory(), host.getCanonicalFileName);
file.fileName = file.originalFileName = fileName;
file.path = file.resolvedPath = path;
}
return file;
}
Loading

0 comments on commit 0b46a49

Please sign in to comment.