Skip to content

Commit

Permalink
added some tests to incremental typescript compiler.
Browse files Browse the repository at this point in the history
  • Loading branch information
0xorial committed Dec 30, 2018
1 parent e5774ed commit 53d690b
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 149 deletions.
78 changes: 38 additions & 40 deletions src/ApiIncrementalChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import * as path from 'path';
import { IncrementalCheckerInterface } from './IncrementalCheckerInterface';
import { CancellationToken } from './CancellationToken';
import { NormalizedMessage } from './NormalizedMessage';
import { Configuration, Linter } from 'tslint';
import { Configuration, Linter, LintResult } from 'tslint';
import { CompilerHost } from './CompilerHost';
import { WorkSet } from './WorkSet';
import { FsHelper } from './FsHelper';

// Need some augmentation here - linterOptions.exclude is not (yet) part of the official
Expand All @@ -21,25 +20,27 @@ interface ConfigurationFile extends Configuration.IConfigurationFile {
export class ApiIncrementalChecker implements IncrementalCheckerInterface {
private linterConfig?: ConfigurationFile;

// Use empty array of exclusions in general to avoid having
// to check of its existence later on.
// private linterExclusions: minimatch.IMinimatch[] = [];

private readonly tsIncrementalCompiler: CompilerHost;
private linterExclusions: minimatch.IMinimatch[] = [];

private currentLintErrors = new Map<string, LintResult>();
private lastUpdatedFiles: string[] = [];
private lastRemovedFiles: string[] = [];

constructor(
programConfigFile: string,
compilerOptions: ts.CompilerOptions,
private linterConfigFile: string | false,
private linterAutoFix: boolean,
private workNumber: number,
private workDivision: number,
checkSyntacticErrors: boolean
) {
this.initLinterConfig();

this.tsIncrementalCompiler = new CompilerHost(programConfigFile, compilerOptions, checkSyntacticErrors);
this.tsIncrementalCompiler = new CompilerHost(
programConfigFile,
compilerOptions,
checkSyntacticErrors
);
}

private initLinterConfig() {
Expand Down Expand Up @@ -80,13 +81,10 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
return !!this.linterConfig;
}

public static isFileExcluded(
filePath: string,
linterExclusions: minimatch.IMinimatch[]
): boolean {
public isFileExcluded(filePath: string): boolean {
return (
filePath.endsWith('.d.ts') ||
linterExclusions.some(matcher => matcher.match(filePath))
this.linterExclusions.some(matcher => matcher.match(filePath))
);
}

Expand All @@ -96,41 +94,35 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {

public async getDiagnostics(_cancellationToken: CancellationToken) {
const diagnostics = await this.tsIncrementalCompiler.processChanges();
this.lastUpdatedFiles = diagnostics.updatedFiles;
this.lastRemovedFiles = diagnostics.removedFiles;

return NormalizedMessage.deduplicate(
diagnostics.map(NormalizedMessage.createFromDiagnostic)
diagnostics.results.map(NormalizedMessage.createFromDiagnostic)
);
}

public getLints(cancellationToken: CancellationToken) {
public getLints(_cancellationToken: CancellationToken) {
if (!this.linterConfig) {
return [];
}

const linter = this.createLinter(this.tsIncrementalCompiler.getProgram());

const files = this.tsIncrementalCompiler.getFiles();

// calculate subset of work to do
const workSet = new WorkSet(
Array.from(files.keys()),
this.workNumber,
this.workDivision
);

// lint given work set
workSet.forEach(fileName => {
cancellationToken.throwIfCancellationRequested();

if (ApiIncrementalChecker.isFileExcluded(fileName, this.linterExclusions)) {
return;
for (const updatedFile of this.lastUpdatedFiles) {
if (this.isFileExcluded(updatedFile)) {
continue;
}

try {
// Assertion: `.lint` second parameter can be undefined
linter.lint(fileName, undefined!, this.linterConfig);
const linter = this.createLinter(
this.tsIncrementalCompiler.getProgram()
);
// const source = fs.readFileSync(updatedFile, 'utf-8');
linter.lint(updatedFile, undefined!, this.linterConfig);
const lints = linter.getResult();
this.currentLintErrors.set(updatedFile, lints);
} catch (e) {
if (
FsHelper.existsSync(fileName) &&
FsHelper.existsSync(updatedFile) &&
// check the error type due to file system lag
!(e instanceof Error) &&
!(e.constructor.name === 'FatalError') &&
Expand All @@ -140,13 +132,19 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
throw e;
}
}
});

const lints = linter.getResult().failures;
for (const removedFile of this.lastRemovedFiles) {
this.currentLintErrors.delete(removedFile);
}
}

const allLints = [];
for (const [, value] of this.currentLintErrors) {
allLints.push(...value.failures);
}

// normalize and deduplicate lints
return NormalizedMessage.deduplicate(
lints.map(NormalizedMessage.createFromLint)
allLints.map(NormalizedMessage.createFromLint)
);
}
}
43 changes: 36 additions & 7 deletions src/CompilerHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ export class CompilerHost
private program?: ts.WatchOfConfigFile<
ts.EmitAndSemanticDiagnosticsBuilderProgram
>;

public getProgram(): ts.Program {
return this.program!.getProgram().getProgram();
}
public getFiles() {

public getAllKnownFiles() {
return this.knownFiles;
}

Expand Down Expand Up @@ -72,7 +74,11 @@ export class CompilerHost
this.optionsToExtend = this.tsHost.optionsToExtend || {};
}

public async processChanges(): Promise<ts.Diagnostic[]> {
public async processChanges(): Promise<{
results: ts.Diagnostic[];
updatedFiles: string[];
removedFiles: string[];
}> {
if (!this.lastProcessing) {
const initialCompile = new Promise<ts.Diagnostic[]>(resolve => {
this.afterCompile = () => {
Expand All @@ -85,7 +91,12 @@ export class CompilerHost
});
this.lastProcessing = initialCompile;
this.program = ts.createWatchProgram(this);
return initialCompile;
const errors = await initialCompile;
return {
results: errors,
updatedFiles: Array.from(this.knownFiles),
removedFiles: []
};
}

// since we do not have a way to pass cancellation token to typescript,
Expand All @@ -94,7 +105,7 @@ export class CompilerHost

const previousDiagnostic = this.gatheredDiagnostic;
this.gatheredDiagnostic = [];
const result = new Promise<ts.Diagnostic[]>(resolve => {
const resultPromise = new Promise<ts.Diagnostic[]>(resolve => {
this.afterCompile = () => {
resolve(this.gatheredDiagnostic);
this.afterCompile = () => {
Expand All @@ -103,7 +114,9 @@ export class CompilerHost
this.compilationStarted = false;
};
});
this.lastProcessing = result;
this.lastProcessing = resultPromise;

const files = [];

this.directoryWatchers.forEach(item => {
for (const e of item.events) {
Expand All @@ -112,9 +125,20 @@ export class CompilerHost
item.events.length = 0;
});

const updatedFiles: string[] = [];
const removedFiles: string[] = [];
this.fileWatchers.forEach(item => {
for (const e of item.events) {
item.callback(e.fileName, e.eventKind);
files.push(e.fileName);
if (
e.eventKind === ts.FileWatcherEventKind.Created ||
e.eventKind === ts.FileWatcherEventKind.Changed
) {
updatedFiles.push(e.fileName);
} else if (e.eventKind === ts.FileWatcherEventKind.Deleted) {
removedFiles.push(e.fileName);
}
}
item.events.length = 0;
});
Expand All @@ -125,10 +149,15 @@ export class CompilerHost
// keep diagnostic from previous run
this.gatheredDiagnostic = previousDiagnostic;
this.afterCompile();
return this.gatheredDiagnostic;
return {
results: this.gatheredDiagnostic,
updatedFiles: [],
removedFiles: []
};
}

return result;
const results = await resultPromise;
return { results, updatedFiles, removedFiles };
}

public setTimeout(
Expand Down
11 changes: 11 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class ForkTsCheckerWebpackPlugin {
private useColors: boolean;
private colors: Chalk;
private formatter: Formatter;
private useTypescriptIncrementalApi: boolean;

private tsconfigPath?: string;
private tslintPath?: string;
Expand Down Expand Up @@ -150,6 +151,9 @@ class ForkTsCheckerWebpackPlugin {
options.formatterOptions || {}
);

this.useTypescriptIncrementalApi =
options.useTypescriptIncrementalApi || false;

this.tsconfigPath = undefined;
this.tslintPath = undefined;
this.watchPaths = [];
Expand Down Expand Up @@ -202,6 +206,13 @@ class ForkTsCheckerWebpackPlugin {
const tsconfigOk = FsHelper.existsSync(this.tsconfigPath);
const tslintOk = !this.tslintPath || FsHelper.existsSync(this.tslintPath);

if (this.useTypescriptIncrementalApi && this.workersNumber !== 1) {
throw new Error(
'Using typescript incremental compilation API ' +
'is currently only allowed with a single worker.'
);
}

// validate logger
if (this.logger) {
if (!this.logger.error || !this.logger.warn || !this.logger.info) {
Expand Down
2 changes: 0 additions & 2 deletions src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ const checker: IncrementalCheckerInterface =
JSON.parse(process.env.COMPILER_OPTIONS!),
process.env.TSLINT === '' ? false : process.env.TSLINT!,
process.env.TSLINTAUTOFIX === 'true',
parseInt(process.env.WORK_NUMBER!, 10) || 0,
parseInt(process.env.WORK_DIVISION!, 10) || 1,
process.env.CHECK_SYNTACTIC_ERRORS === 'true'
)
: new IncrementalChecker(
Expand Down
3 changes: 2 additions & 1 deletion src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
"outDir": "../lib"
"outDir": "../lib",
"sourceMap": true
}
}
105 changes: 105 additions & 0 deletions test/integration/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
var path = require('path');
var webpack = require('webpack');
var ForkTsCheckerWebpackPlugin = require('../../lib/index');
var IncrementalChecker = require('../../lib/IncrementalChecker')
.IncrementalChecker;

var webpackMajorVersion = require('./webpackVersion')();
var VueLoaderPlugin =
webpackMajorVersion >= 4 ? require('vue-loader/lib/plugin') : undefined;

exports.createVueCompiler = function(options) {
var plugin = new ForkTsCheckerWebpackPlugin({ ...options, silent: true });

var compiler = webpack({
...(webpackMajorVersion >= 4 ? { mode: 'development' } : {}),
context: path.resolve(__dirname, './vue'),
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, '../../tmp')
},
resolve: {
extensions: ['.ts', '.js', '.vue', '.json'],
alias: {
'@': path.resolve(__dirname, './vue/src')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
transpileOnly: true,
silent: true
}
},
{
test: /\.css$/,
loader: 'css-loader'
}
]
},
plugins:
webpackMajorVersion >= 4 ? [new VueLoaderPlugin(), plugin] : [plugin]
});

var files = {
'example.vue': path.resolve(compiler.context, 'src/example.vue'),
'syntacticError.ts': path.resolve(compiler.context, 'src/syntacticError.ts')
};

var checker = new IncrementalChecker(
plugin.tsconfigPath,
{},
plugin.tslintPath || false,
plugin.tslintAutoFix || false,
[compiler.context],
ForkTsCheckerWebpackPlugin.ONE_CPU,
1,
plugin.checkSyntacticErrors,
plugin.vue
);

checker.nextIteration();

return { plugin, compiler, files, checker };
};

exports.createCompiler = function(
options,
happyPackMode,
entryPoint = './src/index.ts'
) {
var plugin = new ForkTsCheckerWebpackPlugin({ ...options, silent: true });

var tsLoaderOptions = happyPackMode
? { happyPackMode: true, silent: true }
: { transpileOnly: true, silent: true };

var webpackInstance = webpack({
...(webpackMajorVersion >= 4 ? { mode: 'development' } : {}),
context: path.resolve(__dirname, './project'),
entry: entryPoint,
output: {
path: path.resolve(__dirname, '../../tmp')
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
options: tsLoaderOptions
}
]
},
plugins: [plugin]
});

return { webpack: webpackInstance, plugin };
};

0 comments on commit 53d690b

Please sign in to comment.