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

add eslint typescript support #305

Merged
merged 23 commits into from
Jul 12, 2019
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
20d1be2
test: create test fixture for eslint + activate vscode plugin
johnnyreilly Jun 30, 2019
6621ec4
feat: add eslint option infrastructure
johnnyreilly Jun 30, 2019
014f466
feat: add basic implementation (untested) - might work!
johnnyreilly Jun 30, 2019
f13f6f3
chore: disable eslint in Travis for now
johnnyreilly Jul 1, 2019
6eed41e
test: added first test of eslint message being published
johnnyreilly Jul 1, 2019
68d4463
test: add error test for eslint
johnnyreilly Jul 1, 2019
725ccdb
feat: add eslint support to IncrementalChecker
johnnyreilly Jul 2, 2019
f8b1784
test: remove duplicate test and up test timeout
johnnyreilly Jul 2, 2019
18699eb
docs: add eslint details to readme
johnnyreilly Jul 3, 2019
072c0e8
feat: validate eslint and allow options to be passed
johnnyreilly Jul 3, 2019
f336064
fix: correctly output version
johnnyreilly Jul 4, 2019
38bc8c9
feat: persist eslinter instance between runs
johnnyreilly Jul 4, 2019
7e55814
feat: incrementalchecker no longer recreates eslint each time
johnnyreilly Jul 7, 2019
3c747fc
feat: share eslint between checkers
johnnyreilly Jul 7, 2019
534c773
feat: share eslint logic between checkers
johnnyreilly Jul 7, 2019
32ad926
fix: respond to @piotr-oles PR feedback
johnnyreilly Jul 8, 2019
a0a14ca
fix: delete stale lints first
johnnyreilly Jul 8, 2019
93d4381
fix: addressed comments suggested by @phryneas
johnnyreilly Jul 9, 2019
590557b
Merge branch 'master' into master
johnnyreilly Jul 10, 2019
48f281c
docs: more eslint info in the readme
johnnyreilly Jul 11, 2019
605fa17
fix: respond to review comments from piotr
johnnyreilly Jul 11, 2019
e32fea7
Merge branch 'master' into master
johnnyreilly Jul 11, 2019
66511f5
fix: move cancellation token check
johnnyreilly Jul 11, 2019
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
14 changes: 0 additions & 14 deletions .eslintrc.json

This file was deleted.

1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,3 @@ package-lock.json

# Editor directories and files
.idea
.vscode
phryneas marked this conversation as resolved.
Show resolved Hide resolved
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ install:
- yarn add $WEBPACK $TSLOADER $VUELOADER -D

script:
- yarn lint
- yarn test

env:
Expand Down
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
}
65 changes: 57 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
# Fork TS Checker Webpack Plugin

[![npm version](https://img.shields.io/npm/v/fork-ts-checker-webpack-plugin.svg)](https://www.npmjs.com/package/fork-ts-checker-webpack-plugin)

[![npm beta version](https://img.shields.io/npm/v/fork-ts-checker-webpack-plugin/beta.svg)](https://www.npmjs.com/package/fork-ts-checker-webpack-plugin)

[![build status](https://travis-ci.org/TypeStrong/fork-ts-checker-webpack-plugin.svg?branch=master)](https://travis-ci.org/TypeStrong/fork-ts-checker-webpack-plugin)

[![downloads](http://img.shields.io/npm/dm/fork-ts-checker-webpack-plugin.svg)](https://npmjs.org/package/fork-ts-checker-webpack-plugin)

[![commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)

[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier)

[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)

Webpack plugin that runs TypeScript type checker on a separate process.

## Installation

This plugin requires minimum **webpack 2.3**, **TypeScript 2.1** and optionally **tslint 4.0**
This plugin requires minimum **webpack 2.3**, **TypeScript 2.1** and optionally **ESLint 6.0.0** or **TSLint 4.0**

```sh
npm install --save-dev fork-ts-checker-webpack-plugin
yarn add fork-ts-checker-webpack-plugin --dev
```

Basic webpack config (with [ts-loader](https://github.com/TypeStrong/ts-loader))

```js
var ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

var webpackConfig = {
const webpackConfig = {
context: __dirname, // to automatically find tsconfig.json
entry: './src/index.ts',
module: {
Expand All @@ -44,12 +50,12 @@ var webpackConfig = {

## Motivation

There is already similar solution - [awesome-typescript-loader](https://github.com/s-panferov/awesome-typescript-loader). You can
There was already similar solution - [awesome-typescript-loader](https://github.com/s-panferov/awesome-typescript-loader). You can
add `CheckerPlugin` and delegate checker to the separate process. The problem with `awesome-typescript-loader` was that, in our case,
it was a lot slower than [ts-loader](https://github.com/TypeStrong/ts-loader) on an incremental build (~20s vs ~3s).
Secondly, we use [tslint](https://palantir.github.io/tslint) and we wanted to run this, along with type checker, in a separate process.
This is why we've created this plugin. To provide better performance, plugin reuses Abstract Syntax Trees between compilations and shares
these trees with tslint. It can be scaled with a multi-process mode to utilize maximum CPU power.
Secondly, we used [tslint](https://palantir.github.io/tslint) and we wanted to run this, along with type checker, in a separate process.
This is why this plugin was created. To provide better performance, the plugin reuses Abstract Syntax Trees between compilations and shares
these trees with TSLint. It can be scaled with a multi-process mode to utilize maximum CPU power.

## Modules resolution

Expand All @@ -61,12 +67,47 @@ to compile files (which traverses dependency graph during compilation) - we have

To debug TypeScript's modules resolution, you can use `tsc --traceResolution` command.

## ESLint

[ESLint is the future of linting in the TypeScript world.](https://eslint.org/blog/2019/01/future-typescript-eslint) If you'd like to use eslint with the plugin, supply this option: `eslint: true` and ensure you have the relevant dependencies installed:

`yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --dev`

You should have an ESLint configuration file in your root project directory. Here is a sample `.eslintrc.js` configuration for a TypeScript project:

```js
const path = require('path');
module.exports = {
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
extends: [
'plugin:@typescript-eslint/recommended' // Uses the recommended rules from the @typescript-eslint/eslint-plugin
],
parserOptions: {
project: path.resolve(__dirname, './tsconfig.json'),
tsconfigRootDir: __dirname,
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
},
rules: {
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
}
};
```

There's a good explanation on setting up TypeScript ESLint support by Robert Cooper [here](https://dev.to/robertcoopercode/using-eslint-and-prettier-in-a-typescript-project-53jb).

## TSLint

*[TSLint is being replaced by ESLint](https://medium.com/palantir/tslint-in-2019-1a144c2317a9).
https://eslint.org/blog/2019/01/future-typescript-eslint. As a consequence, support for TSLint in fork-ts-checker-webpack-plugin will be deprecated and removed in future versions of the plugin.*

If you have installed [tslint](https://palantir.github.io/tslint), you can enable it by setting `tslint: true` or
`tslint: './path/to/tslint.json'`. We recommend changing `defaultSeverity` to a `"warning"` in `tslint.json` file.
It helps to distinguish lints from TypeScript's diagnostics.



## Options

- **tsconfig** `string`:
Expand All @@ -75,6 +116,14 @@ It helps to distinguish lints from TypeScript's diagnostics.
- **compilerOptions** `object`:
Allows overriding TypeScript options. Should be specified in the same format as you would do for the `compilerOptions` property in tsconfig.json. Default: `{}`.

- **eslint** `true | undefined`:

- If `true`, this activates eslint support.

- **eslintOptions** `object`:

- Options that can be used to initialise ESLint. See https://eslint.org/docs/1.0.0/developer-guide/nodejs-api#cliengine

- **tslint** `string | true | undefined`:

- If `string`, path to _tslint.json_ file to check source files against.
Expand Down
10 changes: 4 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,6 @@
"git add"
],
"*.ts": [
"tslint --fix",
phryneas marked this conversation as resolved.
Show resolved Hide resolved
"prettier --write",
"git add"
],
"*.{yml,json,md,html}": [
johnnyreilly marked this conversation as resolved.
Show resolved Hide resolved
"prettier --write",
"git add"
]
Expand Down Expand Up @@ -113,6 +108,7 @@
"@commitlint/config-conventional": "^7.5.0",
"@types/babel-code-frame": "^6.20.1",
"@types/chokidar": "^1.7.5",
"@types/eslint": "^4.16.6",
"@types/jest": "^24.0.11",
"@types/lodash": "^4.14.134",
"@types/micromatch": "^3.1.0",
Expand All @@ -122,10 +118,12 @@
"@types/rimraf": "^2.0.2",
"@types/semver": "^5.5.0",
"@types/webpack": "^4.4.19",
"@typescript-eslint/eslint-plugin": "^1.11.0",
"@typescript-eslint/parser": "^1.11.0",
"commitlint": "^7.5.2",
"copy-dir": "^0.4.0",
"css-loader": "0.28.11",
"eslint": "^5.7.0",
"eslint": "^6.0.0",
"git-cz": "^3.0.1",
"husky": "^1.1.4",
"istanbul": "^0.4.5",
Expand Down
9 changes: 9 additions & 0 deletions src/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module'
}
};
93 changes: 71 additions & 22 deletions src/ApiIncrementalChecker.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
// tslint:disable-next-line:no-implicit-dependencies
import * as ts from 'typescript'; // Imported for types alone
// tslint:disable-next-line:no-implicit-dependencies
import { Linter, LintResult, RuleFailure } from 'tslint';
import { Linter, LintResult, RuleFailure } from 'tslint'; // Imported for types alone
// tslint:disable-next-line:no-implicit-dependencies
import * as eslinttypes from 'eslint'; // Imported for types alone
import * as minimatch from 'minimatch';
import * as path from 'path';
import { IncrementalCheckerInterface } from './IncrementalCheckerInterface';
import {
IncrementalCheckerInterface,
ApiIncrementalCheckerParams
} from './IncrementalCheckerInterface';
import { CancellationToken } from './CancellationToken';
import {
ConfigurationFile,
Expand All @@ -13,8 +18,8 @@ import {
} from './linterConfigHelpers';
import { NormalizedMessage } from './NormalizedMessage';
import { CompilerHost } from './CompilerHost';
import { ResolveModuleName, ResolveTypeReferenceDirective } from './resolution';
import { FsHelper } from './FsHelper';
import { fileExistsSync } from './FsHelper';
import { createEslinter } from './createEslinter';

export class ApiIncrementalChecker implements IncrementalCheckerInterface {
private linterConfig?: ConfigurationFile;
Expand All @@ -24,28 +29,47 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
private linterExclusions: minimatch.IMinimatch[] = [];

private currentLintErrors = new Map<string, LintResult>();
private currentEsLintErrors = new Map<
string,
eslinttypes.CLIEngine.LintReport
>();
private lastUpdatedFiles: string[] = [];
private lastRemovedFiles: string[] = [];

private readonly hasFixedConfig: boolean;

constructor(
typescript: typeof ts,
private createNormalizedMessageFromDiagnostic: (
diagnostic: ts.Diagnostic
) => NormalizedMessage,
private createNormalizedMessageFromRuleFailure: (
ruleFailure: RuleFailure
) => NormalizedMessage,
programConfigFile: string,
compilerOptions: ts.CompilerOptions,
private context: string,
private linterConfigFile: string | boolean,
private linterAutoFix: boolean,
checkSyntacticErrors: boolean,
resolveModuleName: ResolveModuleName | undefined,
resolveTypeReferenceDirective: ResolveTypeReferenceDirective | undefined
) {
private readonly context: string;
private readonly createNormalizedMessageFromDiagnostic: (
diagnostic: ts.Diagnostic
) => NormalizedMessage;
private readonly linterConfigFile: string | boolean;
private readonly linterAutoFix: boolean;
private readonly createNormalizedMessageFromRuleFailure: (
ruleFailure: RuleFailure
) => NormalizedMessage;
private readonly eslinter: ReturnType<typeof createEslinter> | undefined;

constructor({
typescript,
context,
programConfigFile,
compilerOptions,
createNormalizedMessageFromDiagnostic,
linterConfigFile,
linterAutoFix,
createNormalizedMessageFromRuleFailure,
eslinter,
checkSyntacticErrors = false,
resolveModuleName,
resolveTypeReferenceDirective
}: ApiIncrementalCheckerParams) {
this.context = context;
this.createNormalizedMessageFromDiagnostic = createNormalizedMessageFromDiagnostic;
this.linterConfigFile = linterConfigFile;
this.linterAutoFix = linterAutoFix;
this.createNormalizedMessageFromRuleFailure = createNormalizedMessageFromRuleFailure;
this.eslinter = eslinter;

this.hasFixedConfig = typeof this.linterConfigFile === 'string';

this.initLinterConfig();
Expand Down Expand Up @@ -97,6 +121,10 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
return !!this.linterConfigFile;
}

public hasEsLinter(): boolean {
return this.eslinter !== undefined;
}

public isFileExcluded(filePath: string): boolean {
return (
filePath.endsWith('.d.ts') ||
Expand Down Expand Up @@ -140,7 +168,7 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
this.currentLintErrors.set(updatedFile, lints);
} catch (e) {
if (
FsHelper.existsSync(updatedFile) &&
fileExistsSync(updatedFile) &&
// check the error type due to file system lag
!(e instanceof Error) &&
!(e.constructor.name === 'FatalError') &&
Expand All @@ -165,4 +193,25 @@ export class ApiIncrementalChecker implements IncrementalCheckerInterface {
allLints.map(this.createNormalizedMessageFromRuleFailure)
);
}

public getEsLints(cancellationToken: CancellationToken) {
cancellationToken.throwIfCancellationRequested();
johnnyreilly marked this conversation as resolved.
Show resolved Hide resolved

for (const removedFile of this.lastRemovedFiles) {
this.currentEsLintErrors.delete(removedFile);
}

for (const updatedFile of this.lastUpdatedFiles) {
if (this.isFileExcluded(updatedFile)) {
continue;
}

johnnyreilly marked this conversation as resolved.
Show resolved Hide resolved
const lints = this.eslinter!.getLints(updatedFile);
if (lints !== undefined) {
this.currentEsLintErrors.set(updatedFile, lints);
}
}

return this.eslinter!.getFormattedLints(this.currentEsLintErrors.values());
}
}
9 changes: 3 additions & 6 deletions src/CancellationToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as path from 'path';
// tslint:disable-next-line:no-implicit-dependencies
import * as ts from 'typescript'; // Imported for types alone

import { FsHelper } from './FsHelper';
import { fileExistsSync } from './FsHelper';

export interface CancellationTokenData {
isCancelled: boolean;
Expand Down Expand Up @@ -60,7 +60,7 @@ export class CancellationToken {
if (duration > 10) {
// check no more than once every 10ms
this.lastCancellationCheckTime = time;
this.isCancelled = FsHelper.existsSync(this.getCancellationFilePath());
this.isCancelled = fileExistsSync(this.getCancellationFilePath());
}

return this.isCancelled;
Expand All @@ -78,10 +78,7 @@ export class CancellationToken {
}

public cleanupCancellation() {
if (
this.isCancelled &&
FsHelper.existsSync(this.getCancellationFilePath())
) {
if (this.isCancelled && fileExistsSync(this.getCancellationFilePath())) {
fs.unlinkSync(this.getCancellationFilePath());
this.isCancelled = false;
}
Expand Down
4 changes: 3 additions & 1 deletion src/FilesRegister.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import * as ts from 'typescript'; // import for types alone
// tslint:disable-next-line:no-implicit-dependencies
import { RuleFailure } from 'tslint'; // import for types alone
import { CLIEngine } from 'eslint'; // import for types alone

export interface DataShape {
source?: ts.SourceFile;
linted: boolean;
lints: RuleFailure[];
tslints: RuleFailure[];
eslints: CLIEngine.LintReport[];
}

export class FilesRegister {
Expand Down
Loading