Vivek More 🧐 212fb00548 cleanup(misc): use more es6 features
- 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
2021-03-24 20:11:32 -04:00

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: {},
},
],
});