Skip to content

Commit

Permalink
cleanup(angular): add @angular-eslint/builder:lint builder migrator (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
leosvelperez committed Nov 14, 2022
1 parent f12186a commit 2a671e7
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 330 deletions.
@@ -0,0 +1,208 @@
import {
ProjectConfiguration,
TargetConfiguration,
Tree,
updateJson,
} from '@nrwl/devkit';
import {
joinPathFragments,
offsetFromRoot,
readJson,
updateProjectConfiguration,
} from '@nrwl/devkit';
import { hasRulesRequiringTypeChecking } from '@nrwl/linter';
import { dirname } from 'path';
import type {
Logger,
ProjectMigrationInfo,
ValidationError,
ValidationResult,
} from '../../utilities';
import { arrayToString } from '../../utilities';
import { BuilderMigrator } from './builder.migrator';

export class AngularEslintLintMigrator extends BuilderMigrator {
private oldEsLintConfigPath: string;
private newEsLintConfigPath: string;

constructor(
tree: Tree,
project: ProjectMigrationInfo,
projectConfig: ProjectConfiguration,
logger: Logger
) {
super(
tree,
'@angular-eslint/builder:lint',
'eslint',
project,
projectConfig,
logger
);
}

override migrate(): void {
for (const [name, target] of this.targets) {
this.oldEsLintConfigPath =
target.options?.eslintConfig ??
joinPathFragments(this.project.oldRoot, '.eslintrc.json');
this.newEsLintConfigPath = this.convertRootPath(this.oldEsLintConfigPath);

this.moveProjectRootFile(this.oldEsLintConfigPath);
this.updateTargetConfiguration(name, target);
this.updateEsLintConfig();
this.updateCacheableOperations([name]);
}

if (!this.targets.size && this.projectConfig.root === '') {
// there could still be a .eslintrc.json file in the root
// so move to new location
const eslintConfig = '.eslintrc.json';
if (this.tree.exists(eslintConfig)) {
this.logger.info(
'No "lint" target was found, but an ESLint config file was found in the project root. The file will be moved to the new location.'
);
this.moveProjectRootFile(eslintConfig);
}
}
}

override validate(): ValidationResult {
const errors: ValidationError[] = [];
// TODO(leo): keeping restriction until the full refactor is done and we start
// expanding what's supported.
if (this.targets.size > 1) {
errors.push({
message: `There is more than one target using a builder that is used to lint the project (${arrayToString(
[...this.targets.keys()]
)}).`,
hint: `Make sure the project only has one target with a builder that is used to lint the project.`,
});
}

return errors.length ? errors : null;
}

private async updateTargetConfiguration(
targetName: string,
target: TargetConfiguration
): Promise<void> {
target.executor = '@nrwl/linter:eslint';

if (!target.options) {
this.logger.warn(
`The target "${targetName}" is not specifying any options. Skipping updating the target configuration.`
);
return;
}

const existEsLintConfigPath = this.tree.exists(this.newEsLintConfigPath);
if (!existEsLintConfigPath) {
this.logger.warn(
`The ESLint config file "${this.oldEsLintConfigPath}" could not be found. Skipping updating the file.`
);
}

target.options.eslintConfig =
target.options.eslintConfig && this.newEsLintConfigPath;
target.options.lintFilePatterns =
target.options.lintFilePatterns &&
target.options.lintFilePatterns.map((pattern) => {
// replace the old source root with the new root, we want to lint all
// matching files in the project, not just the ones in the source root
if (pattern.startsWith(this.project.oldSourceRoot)) {
return joinPathFragments(
this.project.newRoot,
pattern.replace(this.project.oldSourceRoot, '')
);
}

// replace the old root with the new root
if (pattern.startsWith(this.project.oldRoot)) {
return joinPathFragments(
this.project.newRoot,
pattern.replace(this.project.oldRoot, '')
);
}

// do nothing, warn about the pattern
this.logger.warn(
`The lint file pattern "${pattern}" specified in the "${targetName}" target is not contained in the project root or source root. The pattern will not be updated.`
);

return pattern;
});

if (existEsLintConfigPath) {
const eslintConfig = readJson(this.tree, this.newEsLintConfigPath);
if (hasRulesRequiringTypeChecking(eslintConfig)) {
target.options.hasTypeAwareRules = true;
}
}

updateProjectConfiguration(this.tree, this.project.name, {
...this.projectConfig,
});
}

private updateEsLintConfig(): void {
if (!this.tree.exists(this.newEsLintConfigPath)) {
return;
}

updateJson(this.tree, this.newEsLintConfigPath, (json) => {
delete json.root;
json.ignorePatterns = ['!**/*'];

const rootEsLintConfigRelativePath = joinPathFragments(
offsetFromRoot(this.projectConfig.root),
'.eslintrc.json'
);
if (Array.isArray(json.extends)) {
json.extends = json.extends.map((extend: string) =>
this.convertEsLintConfigExtendToNewPath(
this.oldEsLintConfigPath,
extend
)
);

// it might have not been extending from the root config, make sure it does
if (!json.extends.includes(rootEsLintConfigRelativePath)) {
json.extends.unshift(rootEsLintConfigRelativePath);
}
} else {
json.extends = rootEsLintConfigRelativePath;
}

json.overrides?.forEach((override) => {
if (!override.parserOptions?.project) {
return;
}

override.parserOptions.project = [
`${this.projectConfig.root}/tsconfig.*?.json`,
];
});

return json;
});
}

private convertEsLintConfigExtendToNewPath(
eslintConfigPath: string,
extendPath: string
): string {
if (!extendPath.startsWith('..')) {
// we only need to adjust paths that are on a different directory, files
// in the same directory are moved together so their relative paths are
// not changed
return extendPath;
}

return joinPathFragments(
offsetFromRoot(this.project.newRoot),
dirname(eslintConfigPath),
extendPath
);
}
}
Expand Up @@ -11,7 +11,7 @@ import type {
import { Migrator } from '../migrator';

export abstract class BuilderMigrator extends Migrator {
protected targets: Map<string, TargetConfiguration> = new Map();
targets: Map<string, TargetConfiguration> = new Map();

constructor(
tree: Tree,
Expand Down
@@ -1,4 +1,5 @@
export * from './angular-devkit-karma.migrator';
export * from './angular-devkit-ng-packagr.migrator';
export * from './angular-eslint-lint.migrator';
export * from './builder-migrator-class.type';
export * from './builder.migrator';
9 changes: 9 additions & 0 deletions packages/angular/src/generators/ng-add/migrators/migrator.ts
Expand Up @@ -41,6 +41,15 @@ export abstract class Migrator {
}
}

protected convertRootPath(originalPath: string): string {
return originalPath?.startsWith(this.project.oldRoot)
? joinPathFragments(
this.project.newRoot,
originalPath.replace(this.project.oldRoot, '')
)
: originalPath;
}

protected moveFile(from: string, to: string, required: boolean = true): void {
if (!this.tree.exists(from)) {
if (required) {
Expand Down
Expand Up @@ -125,8 +125,8 @@ describe('app migrator', () => {
expect(result[0].messageGroup.messages).toStrictEqual([
'The "build" target is using an unsupported builder "@not/supported:builder".',
]);
expect(result[0].hint).toBe(
'The supported builders for applications are: "@angular-devkit/build-angular:browser", "@angular-devkit/build-angular:protractor", "@cypress/schematic:cypress", "@angular-devkit/build-angular:extract-i18n", "@angular-eslint/builder:lint", "@nguniversal/builders:prerender", "@angular-devkit/build-angular:dev-server", "@angular-devkit/build-angular:server", "@nguniversal/builders:ssr-dev-server" and "@angular-devkit/build-angular:karma".'
expect(result[0].hint).toMatchInlineSnapshot(
`"The supported builders for applications are: \\"@angular-devkit/build-angular:browser\\", \\"@angular-devkit/build-angular:protractor\\", \\"@cypress/schematic:cypress\\", \\"@angular-devkit/build-angular:extract-i18n\\", \\"@nguniversal/builders:prerender\\", \\"@angular-devkit/build-angular:dev-server\\", \\"@angular-devkit/build-angular:server\\", \\"@nguniversal/builders:ssr-dev-server\\", \\"@angular-devkit/build-angular:karma\\" and \\"@angular-eslint/builder:lint\\"."`
);
});

Expand All @@ -148,8 +148,8 @@ describe('app migrator', () => {
'The "build" target is using an unsupported builder "@not/supported:builder".',
'The "test" target is using an unsupported builder "@other/not-supported:builder".',
]);
expect(result[0].hint).toBe(
'The supported builders for applications are: "@angular-devkit/build-angular:browser", "@angular-devkit/build-angular:protractor", "@cypress/schematic:cypress", "@angular-devkit/build-angular:extract-i18n", "@angular-eslint/builder:lint", "@nguniversal/builders:prerender", "@angular-devkit/build-angular:dev-server", "@angular-devkit/build-angular:server", "@nguniversal/builders:ssr-dev-server" and "@angular-devkit/build-angular:karma".'
expect(result[0].hint).toMatchInlineSnapshot(
`"The supported builders for applications are: \\"@angular-devkit/build-angular:browser\\", \\"@angular-devkit/build-angular:protractor\\", \\"@cypress/schematic:cypress\\", \\"@angular-devkit/build-angular:extract-i18n\\", \\"@nguniversal/builders:prerender\\", \\"@angular-devkit/build-angular:dev-server\\", \\"@angular-devkit/build-angular:server\\", \\"@nguniversal/builders:ssr-dev-server\\", \\"@angular-devkit/build-angular:karma\\" and \\"@angular-eslint/builder:lint\\"."`
);
});

Expand All @@ -167,8 +167,8 @@ describe('app migrator', () => {
expect(result[0].messageGroup.messages).toStrictEqual([
'The "my-build" target is using an unsupported builder "@not/supported:builder".',
]);
expect(result[0].hint).toBe(
'The supported builders for applications are: "@angular-devkit/build-angular:browser", "@angular-devkit/build-angular:protractor", "@cypress/schematic:cypress", "@angular-devkit/build-angular:extract-i18n", "@angular-eslint/builder:lint", "@nguniversal/builders:prerender", "@angular-devkit/build-angular:dev-server", "@angular-devkit/build-angular:server", "@nguniversal/builders:ssr-dev-server" and "@angular-devkit/build-angular:karma".'
expect(result[0].hint).toMatchInlineSnapshot(
`"The supported builders for applications are: \\"@angular-devkit/build-angular:browser\\", \\"@angular-devkit/build-angular:protractor\\", \\"@cypress/schematic:cypress\\", \\"@angular-devkit/build-angular:extract-i18n\\", \\"@nguniversal/builders:prerender\\", \\"@angular-devkit/build-angular:dev-server\\", \\"@angular-devkit/build-angular:server\\", \\"@nguniversal/builders:ssr-dev-server\\", \\"@angular-devkit/build-angular:karma\\" and \\"@angular-eslint/builder:lint\\"."`
);
});

Expand Down Expand Up @@ -1578,8 +1578,8 @@ describe('app migrator', () => {
'test',
'e2e',
'myCustomTest',
'myCustomBuild',
'myCustomLint',
'myCustomBuild',
]);
});

Expand Down

0 comments on commit 2a671e7

Please sign in to comment.