215 lines
7.3 KiB
TypeScript
Executable File
215 lines
7.3 KiB
TypeScript
Executable File
import { conversionGenerator as cypressConversionGenerator } from '@nrwl/cypress';
|
|
import {
|
|
convertNxGenerator,
|
|
formatFiles,
|
|
GeneratorCallback,
|
|
logger,
|
|
Tree,
|
|
} from '@nrwl/devkit';
|
|
import { ConvertTSLintToESLintSchema, ProjectConverter } from '@nrwl/linter';
|
|
import type { Linter } from 'eslint';
|
|
import type { AngularProjectConfiguration } from '../../utils/types';
|
|
import { addLintingGenerator } from '../add-linting/add-linting';
|
|
import { warnForSchematicUsage } from '../utils/warn-for-schematic-usage';
|
|
|
|
export async function conversionGenerator(
|
|
host: Tree,
|
|
options: ConvertTSLintToESLintSchema
|
|
) {
|
|
/**
|
|
* The ProjectConverter instance encapsulates all the standard operations we need
|
|
* to perform in order to convert a project from TSLint to ESLint, as well as some
|
|
* extensibility points for adjusting the behavior on a per package basis.
|
|
*
|
|
* E.g. @nrwl/angular projects might need to make different changes to the final
|
|
* ESLint config when compared with @nrwl/next projects.
|
|
*
|
|
* See the ProjectConverter implementation for a full breakdown of what it does.
|
|
*/
|
|
const projectConverter = new ProjectConverter({
|
|
host,
|
|
projectName: options.project,
|
|
ignoreExistingTslintConfig: options.ignoreExistingTslintConfig,
|
|
eslintInitializer: async ({ projectName, projectConfig }) => {
|
|
await addLintingGenerator(host, {
|
|
projectName,
|
|
projectRoot: projectConfig.root,
|
|
prefix: (projectConfig as AngularProjectConfiguration).prefix || 'app',
|
|
/**
|
|
* We set the parserOptions.project config just in case the converted config uses
|
|
* rules which require type-checking. Later in the conversion we check if it actually
|
|
* does and remove the config again if it doesn't, so that it is most efficient.
|
|
*/
|
|
setParserOptionsProject: true,
|
|
skipFormat: true,
|
|
});
|
|
},
|
|
});
|
|
|
|
/**
|
|
* If root eslint configuration already exists it will not be recreated
|
|
* but we also don't want to re-run the tslint config conversion
|
|
* as it was likely already done
|
|
*/
|
|
const rootEslintConfigExists = host.exists('.eslintrc.json');
|
|
/**
|
|
* Create the standard (which is applicable to the current package) ESLint setup
|
|
* for converting the project.
|
|
*/
|
|
const eslintInitInstallTask = await projectConverter.initESLint();
|
|
|
|
/**
|
|
* Convert the root tslint.json and apply the converted rules to the root .eslintrc.json
|
|
*/
|
|
const rootConfigInstallTask = await projectConverter.convertRootTSLintConfig(
|
|
(json) => {
|
|
json.overrides = [
|
|
{ files: ['*.ts'], rules: {} },
|
|
{ files: ['*.html'], rules: {} },
|
|
];
|
|
return applyAngularRulesToCorrectOverrides(json);
|
|
},
|
|
rootEslintConfigExists
|
|
);
|
|
|
|
/**
|
|
* Convert the project's tslint.json to an equivalent ESLint config.
|
|
*/
|
|
const projectConfigInstallTask = await projectConverter.convertProjectConfig(
|
|
(json) => applyAngularRulesToCorrectOverrides(json)
|
|
);
|
|
/**
|
|
* Clean up the original TSLint configuration for the project.
|
|
*/
|
|
projectConverter.removeProjectTSLintFile();
|
|
|
|
// Only project shouldn't be added as a default
|
|
const { project, ...defaults } = options;
|
|
|
|
/**
|
|
* Store user preferences for the collection
|
|
*/
|
|
projectConverter.setDefaults('@nrwl/angular', defaults);
|
|
|
|
/**
|
|
* If the Angular project is an app which has an e2e project, try and convert that as well.
|
|
*/
|
|
let cypressInstallTask: GeneratorCallback = () => Promise.resolve(undefined);
|
|
const e2eProjectName = projectConverter.getE2EProjectName();
|
|
if (e2eProjectName) {
|
|
try {
|
|
cypressInstallTask = await cypressConversionGenerator(host, {
|
|
project: e2eProjectName,
|
|
ignoreExistingTslintConfig: options.ignoreExistingTslintConfig,
|
|
/**
|
|
* We can always set this to false, because it will already be handled by the next
|
|
* step of this parent generator, if applicable
|
|
*/
|
|
removeTSLintIfNoMoreTSLintTargets: false,
|
|
skipFormat: true,
|
|
});
|
|
} catch {
|
|
logger.warn(
|
|
'This Angular app has an e2e project, but it was not possible to convert it from TSLint to ESLint. This could be because the e2e project did not have a tslint.json file to begin with.'
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Based on user preference and remaining usage, remove TSLint from the workspace entirely.
|
|
*/
|
|
let uninstallTSLintTask: GeneratorCallback = () => Promise.resolve(undefined);
|
|
if (
|
|
options.removeTSLintIfNoMoreTSLintTargets &&
|
|
!projectConverter.isTSLintUsedInWorkspace()
|
|
) {
|
|
uninstallTSLintTask = projectConverter.removeTSLintFromWorkspace();
|
|
}
|
|
|
|
if (!options.skipFormat) {
|
|
await formatFiles(host);
|
|
}
|
|
|
|
return async () => {
|
|
await eslintInitInstallTask();
|
|
await rootConfigInstallTask();
|
|
await projectConfigInstallTask();
|
|
await cypressInstallTask();
|
|
await uninstallTSLintTask();
|
|
};
|
|
}
|
|
|
|
export const conversionSchematic = warnForSchematicUsage(
|
|
convertNxGenerator(conversionGenerator)
|
|
);
|
|
|
|
/**
|
|
* In the case of Angular lint rules, we need to apply them to correct override depending upon whether
|
|
* or not they require @typescript-eslint/parser or @angular-eslint/template-parser in order to function.
|
|
*
|
|
* By this point, the applicable overrides have already been scaffolded for us by the Nx generators
|
|
* that ran earlier within this generator.
|
|
*/
|
|
function applyAngularRulesToCorrectOverrides(
|
|
json: Linter.Config
|
|
): Linter.Config {
|
|
const rules = json.rules;
|
|
|
|
if (rules && Object.keys(rules).length) {
|
|
for (const [ruleName, ruleConfig] of Object.entries(rules)) {
|
|
for (const override of json.overrides) {
|
|
if (
|
|
override.files.includes('*.html') &&
|
|
ruleName.startsWith('@angular-eslint/template')
|
|
) {
|
|
// Prioritize the converted rules over any base implementations from the original Nx generator
|
|
override.rules[ruleName] = ruleConfig;
|
|
}
|
|
|
|
/**
|
|
* By default, tslint-to-eslint-config will try and apply any rules without known converters
|
|
* by using eslint-plugin-tslint. We instead explicitly warn the user about this missing converter,
|
|
* and therefore at this point we strip out any rules which start with @typescript-eslint/tslint/config
|
|
*/
|
|
if (
|
|
override.files.includes('*.ts') &&
|
|
!ruleName.startsWith('@angular-eslint/template')
|
|
) {
|
|
// Prioritize the converted rules over any base implementations from the original Nx generator
|
|
override.rules[ruleName] = ruleConfig;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// It's possible that there are plugins to apply to the TS override
|
|
if (json.plugins) {
|
|
for (const override of json.overrides) {
|
|
if (override.files.includes('*.ts')) {
|
|
override.plugins = override.plugins || [];
|
|
override.plugins = [
|
|
...override.plugins,
|
|
...json.plugins.filter(
|
|
(plugin) => plugin !== '@angular-eslint/eslint-plugin-template'
|
|
),
|
|
];
|
|
}
|
|
|
|
if (
|
|
override.files.includes('*.html') &&
|
|
json.plugins.includes('@angular-eslint/eslint-plugin-template')
|
|
) {
|
|
override.plugins = ['@angular-eslint/eslint-plugin-template'];
|
|
}
|
|
}
|
|
delete json.plugins;
|
|
}
|
|
|
|
/**
|
|
* We now no longer need the flat list of rules at the root of the config
|
|
* because they have all been applied to an appropriate override.
|
|
*/
|
|
delete json.rules;
|
|
return json;
|
|
}
|