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

perf(betterer 🏎): only check changed ts files #739

Merged
merged 7 commits into from
Jun 12, 2021
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
2 changes: 2 additions & 0 deletions goldens/api/@betterer/tsquery.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export declare function tsquery(configFilePath: string, query: string): BettererFileTest;

export declare function tsqueryΔ(query: string): BettererFileTest;
2 changes: 2 additions & 0 deletions goldens/api/@betterer/typescript.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export declare function typescript(configFilePath: string, extraCompilerOptions: ts.CompilerOptions): BettererFileTest;

export declare function typescriptΔ(configFilePath: string, extraCompilerOptions?: ts.CompilerOptions): BettererFileTest;
2 changes: 1 addition & 1 deletion packages/tsquery/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { tsquery } from './tsquery';
export { tsquery, tsqueryΔ } from './tsquery';
31 changes: 31 additions & 0 deletions packages/tsquery/src/tsquery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,34 @@ export function tsquery(configFilePath: string, query: string): BettererFileTest
);
});
}

/** @internal Definitely not stable! Please don't use! */
export function tsqueryΔ(query: string): BettererFileTest {
if (!query) {
throw new BettererError(
"for `@betterer/tsquery` to work, you need to provide a query, e.g. `'CallExpression > PropertyAccessExpression'`. ❌"
);
}

const resolver = new BettererFileResolver();
return new BettererFileTest(resolver, async (filePaths, fileTestResult) => {
if (filePaths.length === 0) {
return;
}

await Promise.all(
filePaths.map(async (filePath) => {
const fileText = await fs.readFile(filePath, 'utf8');
const sourceFile = tsq.ast(fileText);
const matches = tsq.query(sourceFile, query, { visitAllChildren: true });
if (matches.length === 0) {
return;
}
const file = fileTestResult.addFile(filePath, fileText);
matches.forEach((match) => {
file.addIssue(match.getStart(), match.getEnd(), 'TSQuery match');
});
})
);
});
}
2 changes: 1 addition & 1 deletion packages/typescript/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { typescript } from './typescript';
export { typescript, typescriptΔ } from './typescript';
74 changes: 73 additions & 1 deletion packages/typescript/src/typescript.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BettererFileResolver, BettererFileTest } from '@betterer/betterer';
import { BettererFileGlobs, BettererFilePaths, BettererFileResolver, BettererFileTest } from '@betterer/betterer';
import { BettererError } from '@betterer/errors';
import * as path from 'path';
import * as ts from 'typescript';
Expand All @@ -8,6 +8,8 @@ const NEW_LINE = '\n';
type TypeScriptReadConfigResult = {
config: {
compilerOptions: ts.CompilerOptions;
files: BettererFilePaths;
include?: BettererFileGlobs;
};
};

Expand Down Expand Up @@ -74,3 +76,73 @@ export function typescript(configFilePath: string, extraCompilerOptions: ts.Comp
});
});
}

/** @internal Definitely not stable! Please don't use! */
export function typescriptΔ(configFilePath: string, extraCompilerOptions: ts.CompilerOptions = {}): BettererFileTest {
if (!configFilePath) {
throw new BettererError(
"for `@betterer/typescript` to work, you need to provide the path to a tsconfig.json file, e.g. `'./tsconfig.json'`. ❌"
);
}

const resolver = new BettererFileResolver();
const absPath = resolver.resolve(configFilePath);

return new BettererFileTest(resolver, (filePaths, fileTestResult) => {
if (filePaths.length === 0) {
return;
}

const { config } = ts.readConfigFile(absPath, ts.sys.readFile.bind(ts.sys)) as TypeScriptReadConfigResult;
const { compilerOptions } = config;
const basePath = path.dirname(absPath);

const fullCompilerOptions = {
...compilerOptions,
...extraCompilerOptions
};
config.compilerOptions = fullCompilerOptions;
config.files = filePaths;
delete config.include;

const compilerHost = ts.createCompilerHost(fullCompilerOptions);
const configHost: ts.ParseConfigHost = {
...compilerHost,
readDirectory: ts.sys.readDirectory.bind(ts.sys),
useCaseSensitiveFileNames: compilerHost.useCaseSensitiveFileNames()
};
const { options, fileNames } = ts.parseJsonConfigFileContent(config, configHost, basePath);
const incrementalHost = ts.createIncrementalCompilerHost(options);

const oldProgram = ts.readBuilderProgram(options, incrementalHost);
const program = oldProgram
? ts.createEmitAndSemanticDiagnosticsBuilderProgram(fileNames, options, incrementalHost, oldProgram)
: ts.createIncrementalProgram({
options,
rootNames: fileNames,
host: incrementalHost
});

const { diagnostics } = program.emit();

const allDiagnostics = ts.sortAndDeduplicateDiagnostics([
...diagnostics,
...program.getConfigFileParsingDiagnostics(),
...program.getOptionsDiagnostics(),
...program.getSyntacticDiagnostics(),
...program.getGlobalDiagnostics(),
...program.getSemanticDiagnostics()
]);

allDiagnostics
.filter(({ file, start, length }) => file && start != null && length != null)
.forEach((diagnostic) => {
const { start, length } = diagnostic as ts.DiagnosticWithLocation;
const source = (diagnostic as ts.DiagnosticWithLocation).file;
const { fileName } = source;
const file = fileTestResult.addFile(fileName, source.getFullText());
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, NEW_LINE);
file.addIssue(start, start + length, message);
});
});
}
135 changes: 135 additions & 0 deletions test/__snapshots__/betterer-typescript.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -563,3 +563,138 @@ Error: for \`@betterer/typescript\` to work, you need to provide compiler option
",
]
`;

exports[`betterer should work with an incremental build 1`] = `
Array [
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🌟 Betterer (0ms):

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🌟 Betterer (0ms): 1 test running...
🤔 typescript incremental: running \\"typescript incremental\\"!

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🌟 Betterer (0ms): 1 test running...
✅ typescript incremental: \\"typescript incremental\\" got checked for the first time! (1 issue) 🎉

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🎉 Betterer (0ms): 1 test done!
✅ typescript incremental: \\"typescript incremental\\" got checked for the first time! (1 issue) 🎉

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🎉 Betterer (0ms): 1 test done!
✅ typescript incremental: \\"typescript incremental\\" got checked for the first time! (1 issue) 🎉

1 test got checked. 🤔
1 test got checked for the first time! 🎉

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🌟 Betterer (0ms):

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🌟 Betterer (0ms): 1 test running...
🤔 typescript incremental: running \\"typescript incremental\\"!

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🌟 Betterer (0ms): 1 test running...
✅ typescript incremental: \\"typescript incremental\\" stayed the same. (1 issue) 😐

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🎉 Betterer (0ms): 1 test done!
✅ typescript incremental: \\"typescript incremental\\" stayed the same. (1 issue) 😐

",
"
/ | / _ _ _
'-.ooo.-' | |__ ___| |_| |_ ___ _ __ ___ _ __
---ooooo--- | '_ // _ / __| __/ _ / '__/ _ / '__|
.-'ooo'-. | |_)| __/ |_| || __/ | | __/ |
/ | / |_.__//___|/__|/__/___|_| /___|_|

🎉 Betterer (0ms): 1 test done!
✅ typescript incremental: \\"typescript incremental\\" stayed the same. (1 issue) 😐

1 test got checked. 🤔
1 test stayed the same. 😐

",
]
`;
73 changes: 73 additions & 0 deletions test/betterer-typescript.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,77 @@ export default {

await cleanup();
});

it('should work with an incremental build', async () => {
const { paths, logs, resolve, cleanup, readFile, writeFile, runNames } = await createFixture(
'test-betterer-typescript-incremental',
{
'.betterer.ts': `
import { typescriptΔ } from '@betterer/typescript';

export default {
'typescript incremental': typescriptΔ('./tsconfig.json', {
incremental: true,
tsBuildInfoFile: './.betterer.tsbuildinfo'
}).include('./src/**/*.ts')
};
`,
'tsconfig.json': `
{
"compilerOptions": {
"noEmit": true,
"lib": ["esnext"],
"moduleResolution": "node",
"target": "ES5",
"typeRoots": ["../../node_modules/@types/"],
"resolveJsonModule": true,
"strict": false
},
"include": [".betterer.ts"]
}
`,
'./src/foo.ts': `
import { bar } from './bar';

export function foo (a: number, b: number, c:number) {
return bar(a * 2, b * 2, c * 2);
}
`,
'./src/bar.ts': `
export function bar (a: number, b: number, c:number) {
return (a * b) ** c;
}
`
}
);

const configPaths = [paths.config];
const resultsPath = paths.results;
const indexPath = resolve('./src/index.ts');
const buildInfoPath = resolve('./.betterer.tsbuildinfo');

await writeFile(indexPath, `import { foo } from './foo';\n\nfoo('a', 'b', 'c');`);

const newStart = new Date().getTime();
const newTestRun = await betterer({ configPaths, resultsPath });
const newTime = new Date().getTime() - newStart;

expect(runNames(newTestRun.new)).toEqual(['typescript incremental']);

const buildInfo = await readFile(buildInfoPath);

expect(buildInfo).not.toBeNull();

const sameStart = new Date().getTime();
const sameTestRun = await betterer({ configPaths, resultsPath });
const sameTime = new Date().getTime() - sameStart;

expect(sameTime).toBeLessThan(newTime);

expect(runNames(sameTestRun.same)).toEqual(['typescript incremental']);

expect(logs).toMatchSnapshot();

await cleanup();
});
});
29 changes: 29 additions & 0 deletions test/runner/__snapshots__/betterer-runner.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`betterer.runner should ignore any files outside of the scope of the tsqueryΔ tsconfig 1`] = `"// BETTERER RESULTS V2."`;

exports[`betterer.runner should ignore any files outside of the scope of the typescriptΔ test glob 1`] = `"// BETTERER RESULTS V2."`;

exports[`betterer.runner should run tsqueryΔ against a file 1`] = `
"// BETTERER RESULTS V2.
exports[\`tsquery no raw console.log\`] = {
value: \`{
\\"src/index.ts:4087252068\\": [
[0, 0, 11, \\"TSQuery match\\", \\"3870399096\\"]
]
}\`
};
"
`;

exports[`betterer.runner should run typescriptΔ against a file 1`] = `
"// BETTERER RESULTS V2.
exports[\`use typescript\`] = {
value: \`{
\\"src/index.ts:1499252024\\": [
[2, 12, 1, \\"The left-hand side of an arithmetic operation must be of type \\\\'any\\\\', \\\\'number\\\\', \\\\'bigint\\\\' or an enum type.\\", \\"177604\\"]
]
}\`
};
"
`;
Loading