- Single char alternation (e.g. a|b|c|d) in a RegExp can be simplified to use a character class ([abcd]) instead. This usually also provides slightly better matching performance. - Character escapes that are replaceable with the unescaped character without a change in meaning. Inside the square brackets of a character class, many escapes are unnecessary that would be necessary outside of a character class. For example the regex [\.] is identical to [.] - If several qualified expressions occur after the qualifier having been checked for nullable, they can be replaced with optional chaining
333 lines
9.3 KiB
TypeScript
333 lines
9.3 KiB
TypeScript
import { join } from '@angular-devkit/core';
|
|
import {
|
|
chain,
|
|
Rule,
|
|
Tree,
|
|
SchematicContext,
|
|
} from '@angular-devkit/schematics';
|
|
import { addDepsToPackageJson } from './ast-utils';
|
|
import {
|
|
eslintVersion,
|
|
typescriptESLintVersion,
|
|
eslintConfigPrettierVersion,
|
|
nxVersion,
|
|
tslintVersion,
|
|
buildAngularVersion,
|
|
} from './versions';
|
|
import { offsetFromRoot } from '@nrwl/devkit';
|
|
|
|
export const enum Linter {
|
|
TsLint = 'tslint',
|
|
EsLint = 'eslint',
|
|
None = 'none',
|
|
}
|
|
|
|
export function generateProjectLint(
|
|
projectRoot: string,
|
|
tsConfigPath: string,
|
|
linter: Linter,
|
|
eslintFilePatterns: string[]
|
|
) {
|
|
if (linter === Linter.TsLint) {
|
|
return {
|
|
builder: '@angular-devkit/build-angular:tslint',
|
|
options: {
|
|
tsConfig: [tsConfigPath],
|
|
exclude: ['**/node_modules/**', `!${projectRoot}/**/*`],
|
|
},
|
|
};
|
|
} else if (linter === Linter.EsLint) {
|
|
return {
|
|
builder: '@nrwl/linter:eslint',
|
|
options: {
|
|
lintFilePatterns: eslintFilePatterns,
|
|
},
|
|
};
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
interface AddLintFileOptions {
|
|
onlyGlobal?: boolean;
|
|
localConfig?: any;
|
|
extraPackageDeps?: {
|
|
dependencies: { [key: string]: string };
|
|
devDependencies: { [key: string]: string };
|
|
};
|
|
}
|
|
export function addLintFiles(
|
|
projectRoot: string,
|
|
linter: Linter,
|
|
options: AddLintFileOptions = {}
|
|
): Rule {
|
|
return (host: Tree, context: SchematicContext) => {
|
|
if (options.onlyGlobal && options.localConfig) {
|
|
throw new Error(
|
|
'onlyGlobal and localConfig cannot be used at the same time'
|
|
);
|
|
}
|
|
|
|
const chainedCommands = [];
|
|
|
|
if (linter === 'tslint') {
|
|
chainedCommands.push((host: Tree) => {
|
|
if (!host.exists('/tslint.json')) {
|
|
host.create('/tslint.json', globalTsLint);
|
|
}
|
|
if (!options.onlyGlobal) {
|
|
host.create(
|
|
join(projectRoot as any, `tslint.json`),
|
|
JSON.stringify({
|
|
extends: `${offsetFromRoot(projectRoot)}tslint.json`,
|
|
// Include project files to be linted since the global one excludes all files.
|
|
linterOptions: {
|
|
exclude: ['!**/*'],
|
|
},
|
|
rules: {},
|
|
})
|
|
);
|
|
}
|
|
});
|
|
|
|
chainedCommands.push(
|
|
addDepsToPackageJson(
|
|
{},
|
|
{
|
|
tslint: tslintVersion,
|
|
'@angular-devkit/build-angular': buildAngularVersion,
|
|
}
|
|
)
|
|
);
|
|
|
|
return chain(chainedCommands);
|
|
}
|
|
|
|
if (linter === 'eslint') {
|
|
if (!host.exists('/.eslintrc.json')) {
|
|
host.create('/.eslintrc.json', globalESLint);
|
|
}
|
|
chainedCommands.push(
|
|
addDepsToPackageJson(
|
|
{
|
|
...(options.extraPackageDeps
|
|
? options.extraPackageDeps.dependencies
|
|
: {}),
|
|
},
|
|
{
|
|
'@nrwl/linter': nxVersion,
|
|
'@nrwl/eslint-plugin-nx': nxVersion,
|
|
'@typescript-eslint/parser': typescriptESLintVersion,
|
|
'@typescript-eslint/eslint-plugin': typescriptESLintVersion,
|
|
eslint: eslintVersion,
|
|
'eslint-config-prettier': eslintConfigPrettierVersion,
|
|
...(options.extraPackageDeps?.devDependencies ?? {}),
|
|
}
|
|
)
|
|
);
|
|
|
|
if (!options.onlyGlobal) {
|
|
chainedCommands.push((host: Tree) => {
|
|
let configJson;
|
|
const rootConfig = `${offsetFromRoot(projectRoot)}.eslintrc.json`;
|
|
|
|
// Include all project files to be linted (since they are turned off in the root eslintrc file).
|
|
const ignorePatterns = ['!**/*'];
|
|
|
|
if (options.localConfig) {
|
|
/**
|
|
* The end config is much easier to reason about if "extends" comes first,
|
|
* so as well as applying the extension from the root lint config, we also
|
|
* adjust the config to make extends come first.
|
|
*/
|
|
const {
|
|
extends: extendsVal,
|
|
...localConfigExceptExtends
|
|
} = options.localConfig;
|
|
|
|
const extendsOption = extendsVal
|
|
? Array.isArray(extendsVal)
|
|
? extendsVal
|
|
: [extendsVal]
|
|
: [];
|
|
|
|
configJson = {
|
|
extends: [...extendsOption, rootConfig],
|
|
ignorePatterns,
|
|
...localConfigExceptExtends,
|
|
};
|
|
} else {
|
|
configJson = {
|
|
extends: rootConfig,
|
|
ignorePatterns,
|
|
overrides: [
|
|
{
|
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
|
parserOptions: {
|
|
/**
|
|
* In order to ensure maximum efficiency when typescript-eslint generates TypeScript Programs
|
|
* behind the scenes during lint runs, we need to make sure the project is configured to use its
|
|
* own specific tsconfigs, and not fall back to the ones in the root of the workspace.
|
|
*/
|
|
project: [`${projectRoot}/tsconfig.*?.json`],
|
|
},
|
|
/**
|
|
* Having an empty rules object present makes it more obvious to the user where they would
|
|
* extend things from if they needed to
|
|
*/
|
|
rules: {},
|
|
},
|
|
{
|
|
files: ['*.ts', '*.tsx'],
|
|
rules: {},
|
|
},
|
|
{
|
|
files: ['*.js', '*.jsx'],
|
|
rules: {},
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
host.create(
|
|
join(projectRoot as any, `.eslintrc.json`),
|
|
JSON.stringify(configJson)
|
|
);
|
|
});
|
|
}
|
|
|
|
return chain(chainedCommands);
|
|
}
|
|
};
|
|
}
|
|
|
|
const globalTsLint = `
|
|
{
|
|
"rulesDirectory": ["node_modules/@nrwl/workspace/src/tslint"],
|
|
"linterOptions": {
|
|
"exclude": ["**/*"]
|
|
},
|
|
"rules": {
|
|
"arrow-return-shorthand": true,
|
|
"callable-types": true,
|
|
"class-name": true,
|
|
"deprecation": {
|
|
"severity": "warn"
|
|
},
|
|
"forin": true,
|
|
"import-blacklist": [true, "rxjs/Rx"],
|
|
"interface-over-type-literal": true,
|
|
"member-access": false,
|
|
"member-ordering": [
|
|
true,
|
|
{
|
|
"order": [
|
|
"static-field",
|
|
"instance-field",
|
|
"static-method",
|
|
"instance-method"
|
|
]
|
|
}
|
|
],
|
|
"no-arg": true,
|
|
"no-bitwise": true,
|
|
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
|
|
"no-construct": true,
|
|
"no-debugger": true,
|
|
"no-duplicate-super": true,
|
|
"no-empty": false,
|
|
"no-empty-interface": true,
|
|
"no-eval": true,
|
|
"no-inferrable-types": [true, "ignore-params"],
|
|
"no-misused-new": true,
|
|
"no-non-null-assertion": true,
|
|
"no-shadowed-variable": true,
|
|
"no-string-literal": false,
|
|
"no-string-throw": true,
|
|
"no-switch-case-fall-through": true,
|
|
"no-unnecessary-initializer": true,
|
|
"no-unused-expression": true,
|
|
"no-var-keyword": true,
|
|
"object-literal-sort-keys": false,
|
|
"prefer-const": true,
|
|
"radix": true,
|
|
"triple-equals": [true, "allow-null-check"],
|
|
"unified-signatures": true,
|
|
"variable-name": false,
|
|
|
|
"nx-enforce-module-boundaries": [
|
|
true,
|
|
{
|
|
"enforceBuildableLibDependency": true,
|
|
"allow": [],
|
|
"depConstraints": [
|
|
{ "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
`;
|
|
|
|
const globalESLint = JSON.stringify({
|
|
root: true,
|
|
ignorePatterns: ['**/*'],
|
|
plugins: ['@nrwl/nx'],
|
|
/**
|
|
* We leverage ESLint's "overrides" capability so that we can set up a root config which will support
|
|
* all permutations of Nx workspaces across all frameworks, libraries and tools.
|
|
*
|
|
* The key point is that we need entirely different ESLint config to apply to different types of files,
|
|
* but we still want to share common config where possible.
|
|
*/
|
|
overrides: [
|
|
/**
|
|
* This configuration is intended to apply to all "source code" (but not
|
|
* markup like HTML, or other custom file types like GraphQL)
|
|
*/
|
|
{
|
|
files: ['*.ts', '*.tsx', '*.js', '*.jsx'],
|
|
rules: {
|
|
'@nrwl/nx/enforce-module-boundaries': [
|
|
'error',
|
|
{
|
|
enforceBuildableLibDependency: true,
|
|
allow: [],
|
|
depConstraints: [
|
|
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
/**
|
|
* This configuration is intended to apply to all TypeScript source files.
|
|
* See the eslint-plugin-nx package for what is in the referenced shareable config.
|
|
*/
|
|
{
|
|
files: ['*.ts', '*.tsx'],
|
|
extends: ['plugin:@nrwl/nx/typescript'],
|
|
/**
|
|
* Having an empty rules object present makes it more obvious to the user where they would
|
|
* extend things from if they needed to
|
|
*/
|
|
rules: {},
|
|
},
|
|
|
|
/**
|
|
* This configuration is intended to apply to all JavaScript source files.
|
|
* See the eslint-plugin-nx package for what is in the referenced shareable config.
|
|
*/
|
|
{
|
|
files: ['*.js', '*.jsx'],
|
|
extends: ['plugin:@nrwl/nx/javascript'],
|
|
/**
|
|
* Having an empty rules object present makes it more obvious to the user where they would
|
|
* extend things from if they needed to
|
|
*/
|
|
rules: {},
|
|
},
|
|
],
|
|
});
|