feat(angular): eslint config including template linting (#3988)
* feat(angular): eslint config including template linting * feat(angular): migration add-template-support-and-presets * feat(angular): add support for component and directive prefix in lint config * fix(angular): tests * fix(angular): e2e tests * fix(angular): update to latest and make updates * fix(angular): update to latest and make updates * fix(angular): lockfile * fix(angular): update to latest and make updates * fix(angular): bump angular-eslint Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
This commit is contained in:
parent
d43a6229c7
commit
bd92a12c33
@ -116,7 +116,7 @@ describe('Angular Package', () => {
|
||||
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
|
||||
}, 1000000);
|
||||
|
||||
it('should support eslint', async () => {
|
||||
it('should support eslint and pass linting on the standard generated code', async () => {
|
||||
const myapp = uniq('myapp');
|
||||
runCLI(`generate @nrwl/angular:app ${myapp} --linter=eslint`);
|
||||
expect(runCLI(`lint ${myapp}`)).toContain('All files pass linting.');
|
||||
@ -125,4 +125,83 @@ describe('Angular Package', () => {
|
||||
runCLI(`generate @nrwl/angular:lib ${mylib} --linter=eslint`);
|
||||
expect(runCLI(`lint ${mylib}`)).toContain('All files pass linting.');
|
||||
});
|
||||
|
||||
it('should support eslint and successfully lint external HTML files and inline templates', async () => {
|
||||
const myapp = uniq('myapp');
|
||||
runCLI(`generate @nrwl/angular:app ${myapp} --linter=eslint`);
|
||||
|
||||
const templateWhichFailsBananaInBoxLintCheck = `<div ([foo])="bar"></div>`;
|
||||
const wrappedAsInlineTemplate = `
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'inline-template-component',
|
||||
template: \`
|
||||
${templateWhichFailsBananaInBoxLintCheck}
|
||||
\`,
|
||||
})
|
||||
export class InlineTemplateComponent {}
|
||||
`;
|
||||
|
||||
// External HTML template file
|
||||
updateFile(
|
||||
`apps/${myapp}/src/app/app.component.html`,
|
||||
templateWhichFailsBananaInBoxLintCheck
|
||||
);
|
||||
|
||||
// Inline template within component.ts file
|
||||
updateFile(
|
||||
`apps/${myapp}/src/app/inline-template.component.ts`,
|
||||
wrappedAsInlineTemplate
|
||||
);
|
||||
|
||||
const appLintStdOut = runCLI(`lint ${myapp}`, { silenceError: true });
|
||||
|
||||
expect(appLintStdOut).toContain(`apps/${myapp}/src/app/app.component.html`);
|
||||
expect(appLintStdOut).toContain(
|
||||
`1:6 error Invalid binding syntax. Use [(expr)] instead @angular-eslint/template/banana-in-box`
|
||||
);
|
||||
expect(appLintStdOut).toContain(
|
||||
`apps/${myapp}/src/app/inline-template.component.ts`
|
||||
);
|
||||
expect(appLintStdOut).toContain(
|
||||
`5:21 error The selector should be prefixed by one of the prefixes: 'proj' (https://angular.io/guide/styleguide#style-02-07) @angular-eslint/component-selector`
|
||||
);
|
||||
expect(appLintStdOut).toContain(
|
||||
`7:18 error Invalid binding syntax. Use [(expr)] instead @angular-eslint/template/banana-in-box`
|
||||
);
|
||||
|
||||
const mylib = uniq('mylib');
|
||||
runCLI(`generate @nrwl/angular:lib ${mylib} --linter=eslint`);
|
||||
|
||||
// External HTML template file
|
||||
updateFile(
|
||||
`libs/${mylib}/src/lib/some.component.html`,
|
||||
templateWhichFailsBananaInBoxLintCheck
|
||||
);
|
||||
|
||||
// Inline template within component.ts file
|
||||
updateFile(
|
||||
`libs/${mylib}/src/lib/inline-template.component.ts`,
|
||||
wrappedAsInlineTemplate
|
||||
);
|
||||
|
||||
const libLintStdOut = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||
|
||||
expect(libLintStdOut).toContain(
|
||||
`libs/${mylib}/src/lib/some.component.html`
|
||||
);
|
||||
expect(libLintStdOut).toContain(
|
||||
`1:6 error Invalid binding syntax. Use [(expr)] instead @angular-eslint/template/banana-in-box`
|
||||
);
|
||||
expect(libLintStdOut).toContain(
|
||||
`libs/${mylib}/src/lib/inline-template.component.ts`
|
||||
);
|
||||
expect(libLintStdOut).toContain(
|
||||
`5:21 error The selector should be prefixed by one of the prefixes: 'proj' (https://angular.io/guide/styleguide#style-02-07) @angular-eslint/component-selector`
|
||||
);
|
||||
expect(libLintStdOut).toContain(
|
||||
`7:18 error Invalid binding syntax. Use [(expr)] instead @angular-eslint/template/banana-in-box`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -32,6 +32,9 @@
|
||||
"@angular-devkit/build-webpack": "~0.1001.3",
|
||||
"@angular-devkit/core": "~10.1.3",
|
||||
"@angular-devkit/schematics": "~10.1.3",
|
||||
"@angular-eslint/eslint-plugin": "0.8.0-beta.1",
|
||||
"@angular-eslint/eslint-plugin-template": "0.8.0-beta.1",
|
||||
"@angular-eslint/template-parser": "0.8.0-beta.1",
|
||||
"@angular/cli": "~10.1.3",
|
||||
"@angular/common": "~10.1.0",
|
||||
"@angular/compiler": "~10.1.0",
|
||||
|
||||
@ -45,6 +45,11 @@
|
||||
"version": "10.4.0-beta.3",
|
||||
"description": "Adjust karma and protractor setup",
|
||||
"factory": "./src/migrations/update-10-4-0/update-10-4-0"
|
||||
},
|
||||
"add-template-support-and-presets-to-eslint": {
|
||||
"version": "10.5.0-beta.0",
|
||||
"description": "Update eslint config and builder to extend from new Nx Angular presets and lint templates",
|
||||
"factory": "./src/migrations/update-10-5-0/add-template-support-and-presets-to-eslint"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
"whitelistedNonPeerDependencies": [
|
||||
"@nrwl/",
|
||||
"@angular-devkit",
|
||||
"@angular-eslint/",
|
||||
"@schematics",
|
||||
"jasmine-marbles"
|
||||
]
|
||||
|
||||
@ -33,13 +33,16 @@
|
||||
"migrations": "./migrations.json"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular-eslint/eslint-plugin": "*",
|
||||
"@angular-eslint/eslint-plugin-template": "*",
|
||||
"@angular-eslint/template-parser": "*",
|
||||
"@nrwl/workspace": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular-devkit/schematics": "~10.1.3",
|
||||
"@nrwl/cypress": "*",
|
||||
"@nrwl/jest": "*",
|
||||
"@nrwl/linter": "*",
|
||||
"@angular-devkit/schematics": "~10.1.3",
|
||||
"@schematics/angular": "~10.1.3",
|
||||
"jasmine-marbles": "~0.6.0"
|
||||
}
|
||||
|
||||
@ -0,0 +1,405 @@
|
||||
import { Tree } from '@angular-devkit/schematics';
|
||||
import { readJsonInTree, updateWorkspace } from '@nrwl/workspace';
|
||||
import { callRule, createEmptyWorkspace } from '@nrwl/workspace/testing';
|
||||
import { runMigration } from '../../utils/testing';
|
||||
|
||||
describe('add-template-support-and-presets-to-eslint', () => {
|
||||
describe('tslint-only workspace', () => {
|
||||
let tree: Tree;
|
||||
beforeEach(async () => {
|
||||
tree = Tree.empty();
|
||||
tree = createEmptyWorkspace(tree);
|
||||
tree = await callRule(
|
||||
updateWorkspace((workspace) => {
|
||||
workspace.projects.add({
|
||||
name: 'app1',
|
||||
root: 'apps/app1',
|
||||
sourceRoot: 'apps/app1/src',
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
lint: {
|
||||
builder: '@angular-devkit/build-angular:tslint',
|
||||
options: {
|
||||
tsConfig: [
|
||||
'apps/app1/tsconfig.app.json',
|
||||
'apps/app1/tsconfig.spec.json',
|
||||
],
|
||||
exclude: ['**/node_modules/**', '!apps/app1/**/*'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
workspace.projects.add({
|
||||
name: 'lib1',
|
||||
root: 'libs/lib1',
|
||||
sourceRoot: 'apps/lib1/src',
|
||||
projectType: 'library',
|
||||
targets: {
|
||||
lint: {
|
||||
builder: '@angular-devkit/build-angular:tslint',
|
||||
options: {
|
||||
tsConfig: [
|
||||
'libs/lib1/tsconfig.app.json',
|
||||
'libs/lib1/tsconfig.spec.json',
|
||||
],
|
||||
exclude: ['**/node_modules/**', '!libs/lib1/**/*'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
tree
|
||||
);
|
||||
});
|
||||
|
||||
it('should do nothing', async () => {
|
||||
const packageJsonBefore = JSON.parse(
|
||||
tree.read('package.json').toString()
|
||||
);
|
||||
|
||||
const result = await runMigration(
|
||||
'add-template-support-and-presets-to-eslint',
|
||||
{},
|
||||
tree
|
||||
);
|
||||
|
||||
expect(packageJsonBefore).toEqual(
|
||||
JSON.parse(result.read('package.json').toString())
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('workspace with at least one eslint project', () => {
|
||||
let tree: Tree;
|
||||
beforeEach(async () => {
|
||||
tree = Tree.empty();
|
||||
tree = createEmptyWorkspace(tree);
|
||||
tree = await callRule(
|
||||
updateWorkspace((workspace) => {
|
||||
workspace.projects.add({
|
||||
name: 'app1',
|
||||
root: 'apps/app1',
|
||||
sourceRoot: 'apps/app1/src',
|
||||
projectType: 'application',
|
||||
prefix: 'customprefix',
|
||||
targets: {
|
||||
lint: {
|
||||
builder: '@nrwl/linter:eslint',
|
||||
options: {
|
||||
lintFilePatterns: ['apps/app1/src/**/*.ts'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// App still using TSLint, will be unaffected
|
||||
workspace.projects.add({
|
||||
name: 'app2',
|
||||
root: 'apps/app2',
|
||||
sourceRoot: 'apps/app2/src',
|
||||
projectType: 'library',
|
||||
targets: {
|
||||
lint: {
|
||||
builder: '@angular-devkit/build-angular:tslint',
|
||||
options: {
|
||||
tsConfig: [
|
||||
'apps/app2/tsconfig.app.json',
|
||||
'apps/app2/tsconfig.spec.json',
|
||||
],
|
||||
exclude: ['**/node_modules/**', '!apps/app2/**/*'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
workspace.projects.add({
|
||||
name: 'lib1',
|
||||
root: 'libs/lib1',
|
||||
sourceRoot: 'libs/lib1/src',
|
||||
projectType: 'application',
|
||||
// No custom prefix, will fall back to npm scope in nx.json
|
||||
prefix: undefined,
|
||||
targets: {
|
||||
lint: {
|
||||
builder: '@nrwl/linter:eslint',
|
||||
options: {
|
||||
lintFilePatterns: ['libs/lib1/src/**/*.ts'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
tree
|
||||
);
|
||||
});
|
||||
|
||||
it('should do nothing if the root eslint config has not been updated to use overrides by the latest migrations', async () => {
|
||||
tree.create(
|
||||
'.eslintrc.json',
|
||||
JSON.stringify({
|
||||
// no overrides here, so can't have been updated/generated by latest Nx
|
||||
rules: {},
|
||||
})
|
||||
);
|
||||
|
||||
const packageJsonBefore = JSON.parse(
|
||||
tree.read('package.json').toString()
|
||||
);
|
||||
|
||||
const result = await runMigration(
|
||||
'add-template-support-and-presets-to-eslint',
|
||||
{},
|
||||
tree
|
||||
);
|
||||
|
||||
expect(packageJsonBefore).toEqual(
|
||||
JSON.parse(result.read('package.json').toString())
|
||||
);
|
||||
});
|
||||
|
||||
it(`should update the workspace package.json if they are using the latest eslint config from Nx`, async () => {
|
||||
tree.create(
|
||||
'.eslintrc.json',
|
||||
JSON.stringify({
|
||||
overrides: [
|
||||
{
|
||||
files: [],
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
const packageJsonBefore = JSON.parse(
|
||||
tree.read('package.json').toString()
|
||||
);
|
||||
|
||||
const result = await runMigration(
|
||||
'add-template-support-and-presets-to-eslint',
|
||||
{},
|
||||
tree
|
||||
);
|
||||
|
||||
expect(packageJsonBefore).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"dependencies": Object {},
|
||||
"devDependencies": Object {},
|
||||
"name": "test-name",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(JSON.parse(result.read('package.json').toString()))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"dependencies": Object {},
|
||||
"devDependencies": Object {
|
||||
"@angular-eslint/eslint-plugin": "0.8.0-beta.1",
|
||||
"@angular-eslint/eslint-plugin-template": "0.8.0-beta.1",
|
||||
"@angular-eslint/template-parser": "0.8.0-beta.1",
|
||||
},
|
||||
"name": "test-name",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it(`should update any relevant project .eslintrc.json files`, async () => {
|
||||
tree.create(
|
||||
'.eslintrc.json',
|
||||
JSON.stringify({
|
||||
overrides: [
|
||||
{
|
||||
files: [],
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
tree.create(
|
||||
'apps/app1/.eslintrc.json',
|
||||
JSON.stringify({
|
||||
rules: {},
|
||||
})
|
||||
);
|
||||
tree.create(
|
||||
'libs/lib1/.eslintrc.json',
|
||||
JSON.stringify({
|
||||
rules: {},
|
||||
})
|
||||
);
|
||||
|
||||
const result = await runMigration(
|
||||
'add-template-support-and-presets-to-eslint',
|
||||
{},
|
||||
tree
|
||||
);
|
||||
|
||||
expect(JSON.parse(result.read('apps/app1/.eslintrc.json').toString()))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"extends": "../../.eslintrc.json",
|
||||
"ignorePatterns": Array [
|
||||
"!**/*",
|
||||
],
|
||||
"overrides": Array [
|
||||
Object {
|
||||
"extends": Array [
|
||||
"plugin:@nrwl/nx/angular",
|
||||
"plugin:@angular-eslint/template/process-inline-templates",
|
||||
],
|
||||
"files": Array [
|
||||
"*.ts",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"apps/app1/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {
|
||||
"@angular-eslint/component-selector": Array [
|
||||
"error",
|
||||
Object {
|
||||
"prefix": "customprefix",
|
||||
"style": "kebab-case",
|
||||
"type": "element",
|
||||
},
|
||||
],
|
||||
"@angular-eslint/directive-selector": Array [
|
||||
"error",
|
||||
Object {
|
||||
"prefix": "customprefix",
|
||||
"style": "camelCase",
|
||||
"type": "attribute",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"extends": Array [
|
||||
"plugin:@nrwl/nx/angular-template",
|
||||
],
|
||||
"files": Array [
|
||||
"*.html",
|
||||
],
|
||||
"rules": Object {},
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
|
||||
expect(JSON.parse(result.read('libs/lib1/.eslintrc.json').toString()))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"extends": "../../.eslintrc.json",
|
||||
"ignorePatterns": Array [
|
||||
"!**/*",
|
||||
],
|
||||
"overrides": Array [
|
||||
Object {
|
||||
"extends": Array [
|
||||
"plugin:@nrwl/nx/angular",
|
||||
"plugin:@angular-eslint/template/process-inline-templates",
|
||||
],
|
||||
"files": Array [
|
||||
"*.ts",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"libs/lib1/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {
|
||||
"@angular-eslint/component-selector": Array [
|
||||
"error",
|
||||
Object {
|
||||
"prefix": "proj",
|
||||
"style": "kebab-case",
|
||||
"type": "element",
|
||||
},
|
||||
],
|
||||
"@angular-eslint/directive-selector": Array [
|
||||
"error",
|
||||
Object {
|
||||
"prefix": "proj",
|
||||
"style": "camelCase",
|
||||
"type": "attribute",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"extends": Array [
|
||||
"plugin:@nrwl/nx/angular-template",
|
||||
],
|
||||
"files": Array [
|
||||
"*.html",
|
||||
],
|
||||
"rules": Object {},
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it(`should update any relevant project builder configuration to include HTML templates`, async () => {
|
||||
tree.create(
|
||||
'.eslintrc.json',
|
||||
JSON.stringify({
|
||||
overrides: [
|
||||
{
|
||||
files: [],
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
tree.create(
|
||||
'apps/app1/.eslintrc.json',
|
||||
JSON.stringify({
|
||||
rules: {},
|
||||
})
|
||||
);
|
||||
tree.create(
|
||||
'libs/lib1/.eslintrc.json',
|
||||
JSON.stringify({
|
||||
rules: {},
|
||||
})
|
||||
);
|
||||
|
||||
const result = await runMigration(
|
||||
'add-template-support-and-presets-to-eslint',
|
||||
{},
|
||||
tree
|
||||
);
|
||||
|
||||
const workspace = readJsonInTree(result, 'workspace.json');
|
||||
|
||||
expect(workspace.projects['app1'].architect).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"lint": Object {
|
||||
"builder": "@nrwl/linter:eslint",
|
||||
"options": Object {
|
||||
"lintFilePatterns": Array [
|
||||
"apps/app1/src/**/*.ts",
|
||||
"apps/app1/src/**/*.html",
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
expect(workspace.projects['lib1'].architect).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"lint": Object {
|
||||
"builder": "@nrwl/linter:eslint",
|
||||
"options": Object {
|
||||
"lintFilePatterns": Array [
|
||||
"libs/lib1/src/**/*.ts",
|
||||
"libs/lib1/src/**/*.html",
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,149 @@
|
||||
import { normalize } from '@angular-devkit/core';
|
||||
import { chain, Rule, Tree } from '@angular-devkit/schematics';
|
||||
import {
|
||||
addDepsToPackageJson,
|
||||
formatFiles,
|
||||
getNpmScope,
|
||||
offsetFromRoot,
|
||||
readJsonInTree,
|
||||
readWorkspace,
|
||||
updateJsonInTree,
|
||||
updateWorkspaceInTree,
|
||||
} from '@nrwl/workspace';
|
||||
import { join } from 'path';
|
||||
|
||||
/**
|
||||
* It was decided with Jason that we would do a simple replacement in this migration
|
||||
* because Angular + ESLint support has been experimental until this point.
|
||||
*/
|
||||
function updateESLintConfigForProject(
|
||||
projectRoot: string,
|
||||
prefix: string
|
||||
): Rule {
|
||||
return updateJsonInTree(join(normalize(projectRoot), '.eslintrc.json'), () =>
|
||||
createAngularEslintJson(projectRoot, prefix)
|
||||
);
|
||||
}
|
||||
|
||||
function addHTMLPatternToBuilderConfig(
|
||||
projectName: string,
|
||||
projectSourceRoot: string,
|
||||
targetName: string
|
||||
): Rule {
|
||||
return updateWorkspaceInTree((workspaceJson) => {
|
||||
workspaceJson.projects[projectName].architect[
|
||||
targetName
|
||||
].options.lintFilePatterns.push(`${projectSourceRoot}/**/*.html`);
|
||||
return workspaceJson;
|
||||
});
|
||||
}
|
||||
|
||||
function updateProjectESLintConfigsAndBuilders(host: Tree): Rule {
|
||||
/**
|
||||
* Make sure user is already using ESLint and is up to date with
|
||||
* previous migrations
|
||||
*/
|
||||
if (!host.exists('.eslintrc.json')) {
|
||||
return;
|
||||
}
|
||||
if (!readJsonInTree(host, '.eslintrc.json').overrides?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workspace = readWorkspace(host);
|
||||
const rules = [];
|
||||
|
||||
let addedExtraDevDeps = false;
|
||||
|
||||
Object.keys(workspace.projects).forEach((projectName) => {
|
||||
const project = workspace.projects[projectName];
|
||||
|
||||
Object.keys(project.architect).forEach((targetName) => {
|
||||
const target = project.architect[targetName];
|
||||
if (target.builder !== '@nrwl/linter:eslint') {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* To reach this point we must have found that at least one project is configured
|
||||
* to use ESLint, therefore we should install the extra devDependencies to ensure
|
||||
* that the updated ESLint config will work correctly
|
||||
*/
|
||||
if (!addedExtraDevDeps) {
|
||||
rules.push(
|
||||
addDepsToPackageJson(
|
||||
{},
|
||||
{
|
||||
'@angular-eslint/eslint-plugin': '0.8.0-beta.1',
|
||||
'@angular-eslint/eslint-plugin-template': '0.8.0-beta.1',
|
||||
'@angular-eslint/template-parser': '0.8.0-beta.1',
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
addedExtraDevDeps = true;
|
||||
}
|
||||
|
||||
// Using the npm scope as the fallback replicates the generation behavior
|
||||
const projectPrefx = project.prefix || getNpmScope(host);
|
||||
|
||||
rules.push(updateESLintConfigForProject(project.root, projectPrefx));
|
||||
|
||||
rules.push(
|
||||
addHTMLPatternToBuilderConfig(
|
||||
projectName,
|
||||
project.sourceRoot,
|
||||
targetName
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return chain(rules);
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return chain([updateProjectESLintConfigsAndBuilders, formatFiles()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is effectively a duplicate of the current (at the time of writing this migration) combined
|
||||
* logic (across workspace utils/lint.ts and angular utils/lint.ts) for an Angular Project's ESLint config.
|
||||
*/
|
||||
function createAngularEslintJson(projectRoot: string, prefix: string) {
|
||||
return {
|
||||
extends: `${offsetFromRoot(projectRoot)}.eslintrc.json`,
|
||||
ignorePatterns: ['!**/*'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts'],
|
||||
extends: [
|
||||
'plugin:@nrwl/nx/angular',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
],
|
||||
parserOptions: {
|
||||
project: [`${projectRoot}/tsconfig.*?.json`],
|
||||
},
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{ type: 'attribute', prefix, style: 'camelCase' },
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{ type: 'element', prefix, style: 'kebab-case' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.html'],
|
||||
extends: ['plugin:@nrwl/nx/angular-template'],
|
||||
/**
|
||||
* Having an empty rules object present makes it more obvious to the user where they would
|
||||
* extend things from if they needed to
|
||||
*/
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@ -1,27 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`app --linter eslint should add an architect target for lint 1`] = `
|
||||
Object {
|
||||
"builder": "@nrwl/linter:eslint",
|
||||
"options": Object {
|
||||
"lintFilePatterns": Array [
|
||||
"apps/my-app/src/**/*.ts",
|
||||
],
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`app --linter eslint should add an architect target for lint 2`] = `
|
||||
Object {
|
||||
"builder": "@nrwl/linter:eslint",
|
||||
"options": Object {
|
||||
"lintFilePatterns": Array [
|
||||
"apps/my-app-e2e/**/*.{js,ts}",
|
||||
],
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`app nested should update workspace.json 1`] = `
|
||||
Object {
|
||||
"architect": Object {
|
||||
|
||||
@ -335,12 +335,85 @@ describe('app', () => {
|
||||
appTree
|
||||
);
|
||||
const workspaceJson = readJsonInTree(tree, 'workspace.json');
|
||||
expect(
|
||||
workspaceJson.projects['my-app'].architect.lint
|
||||
).toMatchSnapshot();
|
||||
expect(
|
||||
workspaceJson.projects['my-app-e2e'].architect.lint
|
||||
).toMatchSnapshot();
|
||||
expect(workspaceJson.projects['my-app'].architect.lint)
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"builder": "@nrwl/linter:eslint",
|
||||
"options": Object {
|
||||
"lintFilePatterns": Array [
|
||||
"apps/my-app/src/**/*.ts",
|
||||
"apps/my-app/src/**/*.html",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(workspaceJson.projects['my-app-e2e'].architect.lint)
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"builder": "@nrwl/linter:eslint",
|
||||
"options": Object {
|
||||
"lintFilePatterns": Array [
|
||||
"apps/my-app-e2e/**/*.{js,ts}",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add valid eslint JSON configuration which extends from Nx presets', async () => {
|
||||
const tree = await runSchematic(
|
||||
'app',
|
||||
{ name: 'myApp', linter: 'eslint' },
|
||||
appTree
|
||||
);
|
||||
|
||||
const eslintConfig = readJsonInTree(tree, 'apps/my-app/.eslintrc.json');
|
||||
|
||||
expect(eslintConfig.overrides).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"extends": Array [
|
||||
"plugin:@nrwl/nx/angular",
|
||||
"plugin:@angular-eslint/template/process-inline-templates",
|
||||
],
|
||||
"files": Array [
|
||||
"*.ts",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"apps/my-app/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {
|
||||
"@angular-eslint/component-selector": Array [
|
||||
"error",
|
||||
Object {
|
||||
"prefix": "proj",
|
||||
"style": "kebab-case",
|
||||
"type": "element",
|
||||
},
|
||||
],
|
||||
"@angular-eslint/directive-selector": Array [
|
||||
"error",
|
||||
Object {
|
||||
"prefix": "proj",
|
||||
"style": "camelCase",
|
||||
"type": "attribute",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"extends": Array [
|
||||
"plugin:@nrwl/nx/angular-template",
|
||||
],
|
||||
"files": Array [
|
||||
"*.html",
|
||||
],
|
||||
"rules": Object {},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -43,8 +43,13 @@ import {
|
||||
updateWorkspaceInTree,
|
||||
appsDir,
|
||||
} from '@nrwl/workspace/src/utils/ast-utils';
|
||||
import {
|
||||
createAngularEslintJson,
|
||||
extraEslintDependencies,
|
||||
} from '../../utils/lint';
|
||||
|
||||
interface NormalizedSchema extends Schema {
|
||||
prefix: string; // we set a default for this in normalizeOptions, so it is no longer optional
|
||||
appProjectRoot: string;
|
||||
e2eProjectName: string;
|
||||
e2eProjectRoot: string;
|
||||
@ -499,6 +504,7 @@ function updateProject(options: NormalizedSchema): Rule {
|
||||
fixedProject.architect.lint.builder = '@nrwl/linter:eslint';
|
||||
fixedProject.architect.lint.options.lintFilePatterns = [
|
||||
`${options.appProjectRoot}/src/**/*.ts`,
|
||||
`${options.appProjectRoot}/src/**/*.html`,
|
||||
];
|
||||
delete fixedProject.architect.lint.options.tsConfig;
|
||||
delete fixedProject.architect.lint.options.exclude;
|
||||
@ -816,6 +822,14 @@ export default function (schema: Schema): Rule {
|
||||
options.routing ? addRouterRootConfiguration(options) : noop(),
|
||||
addLintFiles(options.appProjectRoot, options.linter, {
|
||||
onlyGlobal: options.linter === Linter.TsLint, // local lint files are added differently when tslint
|
||||
localConfig:
|
||||
options.linter === Linter.TsLint
|
||||
? undefined
|
||||
: createAngularEslintJson(options.appProjectRoot, options.prefix),
|
||||
extraPackageDeps:
|
||||
options.linter === Linter.TsLint
|
||||
? undefined
|
||||
: extraEslintDependencies,
|
||||
}),
|
||||
options.linter === 'tslint' ? updateTsLintConfig(options) : noop(),
|
||||
options.unitTestRunner === 'jest'
|
||||
|
||||
@ -9,4 +9,5 @@ export interface NormalizedSchema extends Schema {
|
||||
moduleName: string;
|
||||
projectDirectory: string;
|
||||
parsedTags: string[];
|
||||
prefix: string; // we set a default for this in normalizeOptions, so it is no longer optional
|
||||
}
|
||||
|
||||
@ -170,6 +170,7 @@ export function updateProject(options: NormalizedSchema): Rule {
|
||||
fixedProject.architect.lint.builder = '@nrwl/linter:eslint';
|
||||
fixedProject.architect.lint.options.lintFilePatterns = [
|
||||
`${options.projectRoot}/src/**/*.ts`,
|
||||
`${options.projectRoot}/src/**/*.html`,
|
||||
];
|
||||
delete fixedProject.architect.lint.options.tsConfig;
|
||||
delete fixedProject.architect.lint.options.exclude;
|
||||
|
||||
@ -1204,4 +1204,85 @@ describe('lib', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('--linter', () => {
|
||||
describe('eslint', () => {
|
||||
it('should add an architect target for lint', async () => {
|
||||
const tree = await runSchematic(
|
||||
'lib',
|
||||
{ name: 'myLib', linter: 'eslint' },
|
||||
appTree
|
||||
);
|
||||
const workspaceJson = readJsonInTree(tree, 'workspace.json');
|
||||
expect(workspaceJson.projects['my-lib'].architect.lint)
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"builder": "@nrwl/linter:eslint",
|
||||
"options": Object {
|
||||
"lintFilePatterns": Array [
|
||||
"libs/my-lib/src/**/*.ts",
|
||||
"libs/my-lib/src/**/*.html",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add valid eslint JSON configuration which extends from Nx presets', async () => {
|
||||
const tree = await runSchematic(
|
||||
'lib',
|
||||
{ name: 'myLib', linter: 'eslint' },
|
||||
appTree
|
||||
);
|
||||
|
||||
const eslintConfig = readJsonInTree(tree, 'libs/my-lib/.eslintrc.json');
|
||||
|
||||
expect(eslintConfig.overrides).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"extends": Array [
|
||||
"plugin:@nrwl/nx/angular",
|
||||
"plugin:@angular-eslint/template/process-inline-templates",
|
||||
],
|
||||
"files": Array [
|
||||
"*.ts",
|
||||
],
|
||||
"parserOptions": Object {
|
||||
"project": Array [
|
||||
"libs/my-lib/tsconfig.*?.json",
|
||||
],
|
||||
},
|
||||
"rules": Object {
|
||||
"@angular-eslint/component-selector": Array [
|
||||
"error",
|
||||
Object {
|
||||
"prefix": "proj",
|
||||
"style": "kebab-case",
|
||||
"type": "element",
|
||||
},
|
||||
],
|
||||
"@angular-eslint/directive-selector": Array [
|
||||
"error",
|
||||
Object {
|
||||
"prefix": "proj",
|
||||
"style": "camelCase",
|
||||
"type": "attribute",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"extends": Array [
|
||||
"plugin:@nrwl/nx/angular-template",
|
||||
],
|
||||
"files": Array [
|
||||
"*.html",
|
||||
],
|
||||
"rules": Object {},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -22,6 +22,10 @@ import { updateProject } from './lib/update-project';
|
||||
import { updateTsConfig } from './lib/update-tsconfig';
|
||||
import { Schema } from './schema';
|
||||
import { enableStrictTypeChecking } from './lib/enable-strict-type-checking';
|
||||
import {
|
||||
createAngularEslintJson,
|
||||
extraEslintDependencies,
|
||||
} from '../../utils/lint';
|
||||
|
||||
export default function (schema: Schema): Rule {
|
||||
return (host: Tree): Rule => {
|
||||
@ -47,6 +51,14 @@ export default function (schema: Schema): Rule {
|
||||
}),
|
||||
addLintFiles(options.projectRoot, options.linter, {
|
||||
onlyGlobal: options.linter === Linter.TsLint,
|
||||
localConfig:
|
||||
options.linter === Linter.TsLint
|
||||
? undefined
|
||||
: createAngularEslintJson(options.projectRoot, options.prefix),
|
||||
extraPackageDeps:
|
||||
options.linter === Linter.TsLint
|
||||
? undefined
|
||||
: extraEslintDependencies,
|
||||
}),
|
||||
addUnitTestRunner(options),
|
||||
// TODO: Remove this after Angular 10.1.0
|
||||
|
||||
47
packages/angular/src/utils/lint.ts
Normal file
47
packages/angular/src/utils/lint.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { angularEslintVersion } from './versions';
|
||||
|
||||
export const extraEslintDependencies = {
|
||||
dependencies: {},
|
||||
devDependencies: {
|
||||
'@angular-eslint/eslint-plugin': angularEslintVersion,
|
||||
'@angular-eslint/eslint-plugin-template': angularEslintVersion,
|
||||
'@angular-eslint/template-parser': angularEslintVersion,
|
||||
},
|
||||
};
|
||||
|
||||
export const createAngularEslintJson = (
|
||||
projectRoot: string,
|
||||
prefix: string
|
||||
) => ({
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts'],
|
||||
extends: [
|
||||
'plugin:@nrwl/nx/angular',
|
||||
'plugin:@angular-eslint/template/process-inline-templates',
|
||||
],
|
||||
parserOptions: {
|
||||
project: [`${projectRoot}/tsconfig.*?.json`],
|
||||
},
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{ type: 'attribute', prefix, style: 'camelCase' },
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{ type: 'element', prefix, style: 'kebab-case' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*.html'],
|
||||
extends: ['plugin:@nrwl/nx/angular-template'],
|
||||
/**
|
||||
* Having an empty rules object present makes it more obvious to the user where they would
|
||||
* extend things from if they needed to
|
||||
*/
|
||||
rules: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
@ -5,3 +5,4 @@ export const angularJsVersion = '1.7.9';
|
||||
export const ngrxVersion = '10.0.0';
|
||||
export const rxjsVersion = '~6.5.5';
|
||||
export const jestPresetAngularVersion = '8.3.1';
|
||||
export const angularEslintVersion = '0.8.0-beta.1';
|
||||
|
||||
19
packages/eslint-plugin-nx/src/configs/angular-template.ts
Normal file
19
packages/eslint-plugin-nx/src/configs/angular-template.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* This configuration is intended to be applied to ALL .html files in Angular
|
||||
* projects within an Nx workspace, as well as extracted inline templates from
|
||||
* .component.ts files (or similar).
|
||||
*
|
||||
* It should therefore NOT contain any rules or plugins which are related to
|
||||
* Angular source code.
|
||||
*
|
||||
* NOTE: The processor to extract the inline templates is applied in users'
|
||||
* configs by the relevant schematic.
|
||||
*
|
||||
* This configuration is intended to be combined with other configs from this
|
||||
* package.
|
||||
*/
|
||||
export default {
|
||||
plugins: ['@angular-eslint/template'],
|
||||
extends: ['plugin:@angular-eslint/template/recommended'],
|
||||
rules: {},
|
||||
};
|
||||
16
packages/eslint-plugin-nx/src/configs/angular.ts
Normal file
16
packages/eslint-plugin-nx/src/configs/angular.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* This configuration is intended to be applied to ALL .ts files in Angular
|
||||
* projects within an Nx workspace.
|
||||
*
|
||||
* It should therefore NOT contain any rules or plugins which are related to
|
||||
* Angular Templates, or more cross-cutting concerns which are not specific
|
||||
* to Angular.
|
||||
*
|
||||
* This configuration is intended to be combined with other configs from this
|
||||
* package.
|
||||
*/
|
||||
export default {
|
||||
plugins: ['@angular-eslint'],
|
||||
extends: ['plugin:@angular-eslint/recommended'],
|
||||
rules: {},
|
||||
};
|
||||
@ -4,6 +4,8 @@ import reactTmp from './configs/react-tmp';
|
||||
import reactBase from './configs/react-base';
|
||||
import reactJsx from './configs/react-jsx';
|
||||
import reactTypescript from './configs/react-typescript';
|
||||
import angularCode from './configs/angular';
|
||||
import angularTemplate from './configs/angular-template';
|
||||
|
||||
import enforceModuleBoundaries, {
|
||||
RULE_NAME as enforceModuleBoundariesRuleName,
|
||||
@ -17,6 +19,8 @@ module.exports = {
|
||||
'react-base': reactBase,
|
||||
'react-typescript': reactTypescript,
|
||||
'react-jsx': reactJsx,
|
||||
angular: angularCode,
|
||||
'angular-template': angularTemplate,
|
||||
},
|
||||
rules: {
|
||||
[enforceModuleBoundariesRuleName]: enforceModuleBoundaries,
|
||||
|
||||
23
yarn.lock
23
yarn.lock
@ -200,6 +200,27 @@
|
||||
ora "4.0.3"
|
||||
rxjs "6.5.4"
|
||||
|
||||
"@angular-eslint/eslint-plugin-template@0.8.0-beta.1":
|
||||
version "0.8.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-0.8.0-beta.1.tgz#750675884e161d162afb4e314fc5621275e383a7"
|
||||
integrity sha512-nyy93m+2WBe5Fpc2IKzWPH1bGqNZYd+BU6nYhNssiYXPRcDWBqIsIhEM74dRK/0AN37tUguJ2weZ6xF6fVN8hw==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "4.3.0"
|
||||
|
||||
"@angular-eslint/eslint-plugin@0.8.0-beta.1":
|
||||
version "0.8.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular-eslint/eslint-plugin/-/eslint-plugin-0.8.0-beta.1.tgz#154824ba3fe8589605c71762c793a42936b27f74"
|
||||
integrity sha512-+vCkUpM81qjb0UwxlUUwGML0lLzmnhqf5HHsRzzfwhd0s5g3DPw8w4Z/CDNBagJmTzSUSnH1GF9uEdtyJCEprA==
|
||||
dependencies:
|
||||
"@typescript-eslint/experimental-utils" "4.3.0"
|
||||
|
||||
"@angular-eslint/template-parser@0.8.0-beta.1":
|
||||
version "0.8.0-beta.1"
|
||||
resolved "https://registry.yarnpkg.com/@angular-eslint/template-parser/-/template-parser-0.8.0-beta.1.tgz#a9a9eaccc4536b5edd6c3483b7ff81fc906874e3"
|
||||
integrity sha512-fiLfwlWWwYz657SxcNfPKsl4HiItqj7mNZuMPlxsiKSyT/+pwTNzMttCafy2v0144SNmHEslZS1nQfc1Nq715g==
|
||||
dependencies:
|
||||
eslint-scope "^5.1.0"
|
||||
|
||||
"@angular/cli@~10.1.3":
|
||||
version "10.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-10.1.3.tgz#188f99583814e97727787869065d228c1b1f4407"
|
||||
@ -10806,7 +10827,7 @@ eslint-scope@^5.0.0:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-scope@^5.1.1:
|
||||
eslint-scope@^5.1.0, eslint-scope@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user