Skip to content

Commit

Permalink
feat: add context option for typescript reporter (#430)
Browse files Browse the repository at this point in the history
Add `context` option to support usage of external tsconfig.json files.

✅ Closes: #297
  • Loading branch information
piotr-oles committed May 28, 2020
1 parent 503a948 commit a430979
Show file tree
Hide file tree
Showing 26 changed files with 407 additions and 150 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -5,7 +5,7 @@
lib

# Package archive used by e2e tests
fork-ts-checker-webpack-plugin.tgz
fork-ts-checker-webpack-plugin-0.0.0-semantic-release.tgz

# Coverage directory used by tools like istanbul
coverage
Expand Down
21 changes: 11 additions & 10 deletions README.md
Expand Up @@ -153,16 +153,17 @@ Options passed to the plugin constructor will overwrite options from the cosmico

Options for the TypeScript checker (`typescript` option object).

| Name | Type | Default value | Description |
| -------------------- | --------------------- | ------------------------------------------------------------------------- | ----------- |
| `enabled` | `boolean` | `true` | If `true`, it enables TypeScript checker. |
| `memoryLimit` | `number` | `2048` | Memory limit for the checker process in MB. If the process exits with the allocation failed error, try to increase this number. |
| `tsconfig` | `string` | `'tsconfig.json'` | Path to the `tsconfig.json` file (path relative to the `compiler.options.context` or absolute path) |
| `build` | `boolean` | `false` | If truthy, it's the equivalent of the `--build` flag for the `tsc` command. |
| `mode` | `'readonly'` or `'write-tsbuildinfo'` or `'write-references'` | `'readonly'` | If you use the `babel-loader`, it's recommended to use `write-references` mode to improve initial compilation time. If you use `ts-loader`, it's recommended to use `readonly` mode to not overwrite filed emitted by `ts-loader`. |
| `compilerOptions` | `object` | `{ skipLibCheck: true, skipDefaultLibCheck: true }` | These options will overwrite compiler options from the `tsconfig.json` file. |
| `diagnosticsOptions` | `object` | `{ syntactic: false, semantic: true, declaration: false, global: false }` | Settings to select which diagnostics do we want to perform. |
| `extensions` | `object` | `{}` | See [TypeScript extensions options](#typescript-extensions-options). |
| Name | Type | Default value | Description |
| -------------------- | --------- | ------------------------------------------------------------------------- | ----------- |
| `enabled` | `boolean` | `true` | If `true`, it enables TypeScript checker. |
| `memoryLimit` | `number` | `2048` | Memory limit for the checker process in MB. If the process exits with the allocation failed error, try to increase this number. |
| `tsconfig` | `string` | `'tsconfig.json'` | Path to the `tsconfig.json` file (path relative to the `compiler.options.context` or absolute path) |
| `context` | `string` | `dirname(configuration.tsconfig)` | The base path for finding files specified in the `tsconfig.json`. Same as the `context` option from the [ts-loader](https://github.com/TypeStrong/ts-loader#context). Useful if you want to keep your `tsconfig.json` in an external package. Keep in mind that **not** having a `tsconfig.json` in your project root can cause different behaviour between `fork-ts-checker-webpack-plugin` and `tsc`. When using editors like `VS Code` it is advised to add a `tsconfig.json` file to the root of the project and extend the config file referenced in option `tsconfig`. |
| `build` | `boolean` | `false` | The equivalent of the `--build` flag for the `tsc` command. To enable `incremental` mode, set it in the `tsconfig.json` file. |
| `mode` | `'readonly'` or `'write-tsbuildinfo'` or `'write-references'` | `'readonly'` | If you use the `babel-loader`, it's recommended to use `write-references` mode to improve initial compilation time. If you use `ts-loader`, it's recommended to use `readonly` mode to not overwrite filed emitted by `ts-loader`. |
| `compilerOptions` | `object` | `{ skipLibCheck: true, skipDefaultLibCheck: true }` | These options will overwrite compiler options from the `tsconfig.json` file. |
| `diagnosticsOptions` | `object` | `{ syntactic: false, semantic: true, declaration: false, global: false }` | Settings to select which diagnostics do we want to perform. |
| `extensions` | `object` | `{}` | See [TypeScript extensions options](#typescript-extensions-options). |

#### TypeScript extensions options

Expand Down
2 changes: 1 addition & 1 deletion examples/eslint/package.json
@@ -1,5 +1,5 @@
{
"name": "fork-ts-checker-webpack-plugin-ts-loader-example",
"name": "fork-ts-checker-webpack-plugin-eslint-example",
"version": "0.0.0",
"main": "dist/index.js",
"license": "MIT",
Expand Down
4 changes: 1 addition & 3 deletions package.json
Expand Up @@ -35,8 +35,7 @@
"lint": "cross-env eslint ./src ./test --ext .ts",
"test": "yarn build && yarn test:unit && yarn test:e2e",
"test:unit": "cross-env jest unit",
"test:pack": "yarn pack --filename fork-ts-checker-webpack-plugin.tgz",
"test:e2e": "yarn test:pack && cross-env jest e2e --ci --runInBand --bail --verbose",
"test:e2e": "npm pack && cross-env jest e2e --ci --runInBand --bail --verbose",
"precommit": "cross-env lint-staged && yarn build && yarn test:unit",
"commit": "cross-env git-cz",
"semantic-release": "semantic-release"
Expand Down Expand Up @@ -112,7 +111,6 @@
"tree-kill": "^1.2.2",
"ts-jest": "^25.3.1",
"typescript": "^3.8.3",
"unixify": "^1.0.0",
"webpack": "^4.42.1"
},
"engines": {
Expand Down
2 changes: 2 additions & 0 deletions src/ForkTsCheckerWebpackPlugin.ts
Expand Up @@ -14,6 +14,7 @@ import { createEsLintReporterRpcClient } from './eslint-reporter/reporter/EsLint
import { tapStartToConnectAndRunReporter } from './hooks/tapStartToConnectAndRunReporter';
import { tapStopToDisconnectReporter } from './hooks/tapStopToDisconnectReporter';
import { tapDoneToCollectRemoved } from './hooks/tapDoneToCollectRemoved';
import { tapAfterCompileToAddDependencies } from './hooks/tapAfterCompileToAddDependencies';
import { tapErrorToLogMessage } from './hooks/tapErrorToLogMessage';
import { getForkTsCheckerWebpackPluginHooks } from './hooks/pluginHooks';

Expand Down Expand Up @@ -59,6 +60,7 @@ class ForkTsCheckerWebpackPlugin implements webpack.Plugin {

tapStartToConnectAndRunReporter(compiler, reporter, configuration, state);
tapDoneToCollectRemoved(compiler, configuration, state);
tapAfterCompileToAddDependencies(compiler, configuration);
tapStopToDisconnectReporter(compiler, reporter, state);
tapErrorToLogMessage(compiler, configuration);
} else {
Expand Down
8 changes: 4 additions & 4 deletions src/ForkTsCheckerWebpackPluginOptions.json
Expand Up @@ -117,6 +117,10 @@
"type": "string",
"description": "Path to tsconfig.json. By default plugin uses context or process.cwd() to localize tsconfig.json file."
},
"context": {
"type": "string",
"description": "The base path for finding files specified in the tsconfig.json. Same as context option from the ts-loader."
},
"build": {
"type": "boolean",
"description": "The equivalent of the `--build` flag from the `tsc`."
Expand All @@ -126,10 +130,6 @@
"enum": ["readonly", "write-tsbuildinfo", "write-references"],
"description": "`readonly` keeps all emitted files in memory, `write-tsbuildinfo` which writes only .tsbuildinfo files and `write-references` which writes both .tsbuildinfo and referenced projects output"
},
"incremental": {
"type": "boolean",
"description": "The equivalent of the `--incremental` flag from the `tsc`."
},
"compilerOptions": {
"type": "object",
"description": "Custom compilerOptions to be passed to the TypeScript compiler.",
Expand Down
22 changes: 15 additions & 7 deletions src/formatter/WebpackFormatter.ts
@@ -1,19 +1,27 @@
import os from 'os';
import chalk from 'chalk';
import { relative } from 'path';
import path from 'path';
import { Formatter } from './Formatter';
import { formatIssueLocation } from '../issue';
import normalizeSlash from '../utils/path/normalizeSlash';
import forwardSlash from '../utils/path/forwardSlash';

function createWebpackFormatter(formatter: Formatter, context: string): Formatter {
// mimics webpack error formatter
return function webpackFormatter(issue) {
const color = issue.severity === 'warning' ? chalk.yellow.bold : chalk.red.bold;

const severity = issue.severity.toUpperCase();
const file = issue.file ? normalizeSlash(relative(context, issue.file)) : undefined;
const location = issue.location ? formatIssueLocation(issue.location) : undefined;
const color = issue.severity === 'warning' ? chalk.yellow : chalk.red;
const header = [severity, 'in', file].concat(location ? [location] : []).join(' ');

return [color.bold(header), formatter(issue), ''].join(os.EOL);
if (issue.file) {
let location = forwardSlash(path.relative(context, issue.file));
if (issue.location) {
location += ` ${formatIssueLocation(issue.location)}`;
}

return [color(`${severity} in ${location}`), formatter(issue), ''].join(os.EOL);
} else {
return [color(`${severity} in `) + formatter(issue), ''].join(os.EOL);
}
};
}

Expand Down
17 changes: 17 additions & 0 deletions src/hooks/tapAfterCompileToAddDependencies.ts
@@ -0,0 +1,17 @@
import webpack from 'webpack';
import path from 'path';
import { ForkTsCheckerWebpackPluginConfiguration } from '../ForkTsCheckerWebpackPluginConfiguration';

function tapAfterCompileToAddDependencies(
compiler: webpack.Compiler,
configuration: ForkTsCheckerWebpackPluginConfiguration
) {
compiler.hooks.afterCompile.tap('ForkTsCheckerWebpackPlugin', (compilation) => {
if (configuration.typescript.enabled) {
// watch tsconfig.json file
compilation.fileDependencies.add(path.normalize(configuration.typescript.tsconfig));
}
});
}

export { tapAfterCompileToAddDependencies };
4 changes: 2 additions & 2 deletions src/issue/IssueMatch.ts
Expand Up @@ -2,7 +2,7 @@ import { Issue } from './index';
import { IssuePredicate } from './IssuePredicate';
import minimatch from 'minimatch';
import path from 'path';
import normalizeSlash from '../utils/path/normalizeSlash';
import forwardSlash from '../utils/path/forwardSlash';

type IssueMatch = Partial<Pick<Issue, 'origin' | 'severity' | 'code' | 'file'>>;

Expand All @@ -14,7 +14,7 @@ function createIssuePredicateFromIssueMatch(context: string, match: IssueMatch):
const matchesFile =
!issue.file ||
(!!issue.file &&
(!match.file || minimatch(normalizeSlash(path.relative(context, issue.file)), match.file)));
(!match.file || minimatch(forwardSlash(path.relative(context, issue.file)), match.file)));

return matchesOrigin && matchesSeverity && matchesCode && matchesFile;
};
Expand Down
8 changes: 4 additions & 4 deletions src/issue/IssueWebpackError.ts
@@ -1,7 +1,7 @@
import { relative } from 'path';
import { Issue } from './Issue';
import { formatIssueLocation } from './IssueLocation';
import normalizeSlash from '../utils/path/normalizeSlash';
import forwardSlash from '../utils/path/forwardSlash';

class IssueWebpackError extends Error {
readonly hideStack = true;
Expand All @@ -14,11 +14,11 @@ class IssueWebpackError extends Error {
// should be a NormalModule instance.
// to avoid such a dependency, we do a workaround - error.file will contain formatted location instead
if (issue.file) {
const parts = [normalizeSlash(relative(context, issue.file))];
this.file = forwardSlash(relative(context, issue.file));

if (issue.location) {
parts.push(formatIssueLocation(issue.location));
this.file += ` ${formatIssueLocation(issue.location)}`;
}
this.file = parts.join(' ');
}

Error.captureStackTrace(this, this.constructor);
Expand Down
40 changes: 17 additions & 23 deletions src/typescript-reporter/TypeScriptReporterConfiguration.ts
@@ -1,21 +1,21 @@
import webpack from 'webpack';
import path from 'path';
import ts from 'typescript';
import { TypeScriptDiagnosticsOptions } from './TypeScriptDiagnosticsOptions';
import { TypeScriptReporterOptions } from './TypeScriptReporterOptions';
import {
createTypeScriptVueExtensionConfiguration,
TypeScriptVueExtensionConfiguration,
} from './extension/vue/TypeScriptVueExtensionConfiguration';
import normalizeSlash from '../utils/path/normalizeSlash';

interface TypeScriptReporterConfiguration {
enabled: boolean;
memoryLimit: number;
tsconfig: string;
build: boolean;
context: string;
mode: 'readonly' | 'write-tsbuildinfo' | 'write-references';
compilerOptions: Partial<ts.CompilerOptions>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
compilerOptions: any;
diagnosticOptions: TypeScriptDiagnosticsOptions;
extensions: {
vue: TypeScriptVueExtensionConfiguration;
Expand All @@ -26,49 +26,43 @@ function createTypeScriptReporterConfiguration(
compiler: webpack.Compiler,
options: TypeScriptReporterOptions | undefined
): TypeScriptReporterConfiguration {
let tsconfig: string =
let tsconfig =
typeof options === 'object' ? options.tsconfig || 'tsconfig.json' : 'tsconfig.json';

// ensure that `tsconfig` is an absolute path with normalized slash
tsconfig = normalizeSlash(
// ensure that `tsconfig` is an absolute normalized path
tsconfig = path.normalize(
path.isAbsolute(tsconfig)
? tsconfig
: path.resolve(compiler.options.context || process.cwd(), tsconfig)
);

// convert json compilerOptions to ts.CompilerOptions
const convertResults = ts.convertCompilerOptionsFromJson(
{
skipDefaultLibCheck: true,
skipLibCheck: true,
...(typeof options === 'object' ? options.compilerOptions || {} : {}),
},
compiler.options.context || process.cwd(),
tsconfig
);
const convertedOptions = convertResults.options || {};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { configFilePath, ...compilerOptions } = convertedOptions;
const optionsAsObject: Exclude<TypeScriptReporterOptions, boolean> =
typeof options === 'object' ? options : {};

return {
enabled: options !== false,
memoryLimit: 2048,
build: false,
mode: 'readonly',
...(typeof options === 'object' ? options : {}),
...optionsAsObject,
tsconfig: tsconfig,
compilerOptions: compilerOptions,
context: optionsAsObject.context || path.dirname(tsconfig),
compilerOptions: {
skipDefaultLibCheck: true,
skipLibCheck: true,
...(optionsAsObject.compilerOptions || {}),
},
extensions: {
vue: createTypeScriptVueExtensionConfiguration(
typeof options === 'object' && options.extensions ? options.extensions.vue : undefined
optionsAsObject.extensions ? optionsAsObject.extensions.vue : undefined
),
},
diagnosticOptions: {
syntactic: false, // by default they are reported by the loader
semantic: true,
declaration: false,
global: false,
...((typeof options === 'object' && options.diagnosticOptions) || {}),
...(optionsAsObject.diagnosticOptions || {}),
},
};
}
Expand Down
1 change: 1 addition & 0 deletions src/typescript-reporter/TypeScriptReporterOptions.ts
Expand Up @@ -7,6 +7,7 @@ type TypeScriptReporterOptions =
enabled?: boolean;
memoryLimit?: number;
tsconfig?: string;
context?: string;
build?: boolean;
mode?: 'readonly' | 'write-tsbuildinfo' | 'write-references';
compilerOptions?: object;
Expand Down
6 changes: 4 additions & 2 deletions src/typescript-reporter/issue/TypeScriptIssueFactory.ts
Expand Up @@ -43,8 +43,10 @@ function createIssueFromTsDiagnostic(diagnostic: ts.Diagnostic): Issue {
};
}

function createIssuesFromTsDiagnostics(diagnostic: ts.Diagnostic[]): Issue[] {
return deduplicateAndSortIssues(diagnostic.map(createIssueFromTsDiagnostic));
function createIssuesFromTsDiagnostics(diagnostics: ts.Diagnostic[]): Issue[] {
return deduplicateAndSortIssues(
diagnostics.map((diagnostic) => createIssueFromTsDiagnostic(diagnostic))
);
}

export { createIssuesFromTsDiagnostics };
@@ -1,7 +1,7 @@
import * as ts from 'typescript';
import { dirname } from 'path';
import { createPassiveFileSystem } from '../file-system/PassiveFileSystem';
import normalizeSlash from '../../utils/path/normalizeSlash';
import forwardSlash from '../../utils/path/forwardSlash';
import { createRealFileSystem } from '../file-system/RealFileSystem';

interface ControlledTypeScriptSystem extends ts.System {
Expand Down Expand Up @@ -82,7 +82,7 @@ function createControlledTypeScriptSystem(
const fileWatchers = fileWatchersMap.get(normalizedPath);
if (fileWatchers) {
// typescript expects normalized paths with posix forward slash
fileWatchers.forEach((fileWatcher) => fileWatcher(normalizeSlash(normalizedPath), event));
fileWatchers.forEach((fileWatcher) => fileWatcher(forwardSlash(normalizedPath), event));
}
}

Expand All @@ -93,15 +93,15 @@ function createControlledTypeScriptSystem(
const directoryWatchers = directoryWatchersMap.get(directory);
if (directoryWatchers) {
directoryWatchers.forEach((directoryWatcher) =>
directoryWatcher(normalizeSlash(normalizedPath))
directoryWatcher(forwardSlash(normalizedPath))
);
}

while (directory !== dirname(directory)) {
const recursiveDirectoryWatchers = recursiveDirectoryWatchersMap.get(directory);
if (recursiveDirectoryWatchers) {
recursiveDirectoryWatchers.forEach((recursiveDirectoryWatcher) =>
recursiveDirectoryWatcher(normalizeSlash(normalizedPath))
recursiveDirectoryWatcher(forwardSlash(normalizedPath))
);
}

Expand Down

0 comments on commit a430979

Please sign in to comment.