import { Tree } from 'nx/src/generators/tree'; import { Linter, lintProjectGenerator } from '@nx/linter'; import { joinPathFragments } from 'nx/src/utils/path'; import { addDependenciesToPackageJson, runTasksInSerial, updateJson, } from '@nx/devkit'; import { extraEslintDependencies } from './lint'; import { addExtendsToLintConfig, isEslintConfigSupported, } from '@nx/linter/src/generators/utils/eslint-file'; export async function addLinting( host: Tree, options: { linter: Linter; name: string; projectRoot: string; unitTestRunner?: 'jest' | 'vitest' | 'none'; setParserOptionsProject?: boolean; skipPackageJson?: boolean; rootProject?: boolean; }, projectType: 'lib' | 'app' ) { if (options.linter === Linter.EsLint) { const lintTask = await lintProjectGenerator(host, { linter: options.linter, project: options.name, tsConfigPaths: [ joinPathFragments(options.projectRoot, `tsconfig.${projectType}.json`), ], unitTestRunner: options.unitTestRunner, eslintFilePatterns: [`${options.projectRoot}/**/*.{ts,tsx,js,jsx,vue}`], skipFormat: true, setParserOptionsProject: options.setParserOptionsProject, rootProject: options.rootProject, }); if (isEslintConfigSupported(host)) { addExtendsToLintConfig(host, options.projectRoot, [ 'plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', '@vue/eslint-config-prettier/skip-formatting', ]); } editEslintConfigFiles(host, options.projectRoot, options.rootProject); let installTask = () => {}; if (!options.skipPackageJson) { installTask = addDependenciesToPackageJson( host, extraEslintDependencies.dependencies, extraEslintDependencies.devDependencies ); } return runTasksInSerial(lintTask, installTask); } else { return () => {}; } } function editEslintConfigFiles( tree: Tree, projectRoot: string, rootProject?: boolean ) { if (tree.exists(joinPathFragments(projectRoot, 'eslint.config.js'))) { const fileName = joinPathFragments(projectRoot, 'eslint.config.js'); updateJson(tree, fileName, (json) => { let updated = false; for (let override of json.overrides) { if (override.parserOptions) { if (!override.files.includes('*.vue')) { override.files.push('*.vue'); } updated = true; } } if (!updated) { json.overrides = [ { files: ['*.ts', '*.tsx', '*.js', '*.jsx', '*.vue'], rules: {}, }, ]; } return json; }); } else { const fileName = joinPathFragments(projectRoot, '.eslintrc.json'); updateJson(tree, fileName, (json) => { let updated = false; for (let override of json.overrides) { if (override.parserOptions) { if (!override.files.includes('*.vue')) { override.files.push('*.vue'); } updated = true; } } if (!updated) { json.overrides = [ { files: ['*.ts', '*.tsx', '*.js', '*.jsx', '*.vue'], rules: {}, }, ]; } return json; }); } // Edit root config too if (tree.exists('.eslintrc.base.json')) { updateJson(tree, '.eslintrc.base.json', (json) => { for (let override of json.overrides) { if ( override.rules && '@nx/enforce-module-boundaries' in override.rules ) { if (!override.files.includes('*.vue')) { override.files.push('*.vue'); } } } return json; }); } else if (tree.exists('.eslintrc.json') && !rootProject) { updateJson(tree, '.eslintrc.json', (json) => { for (let override of json.overrides) { if ( override.rules && '@nx/enforce-module-boundaries' in override.rules ) { if (!override.files.includes('*.vue')) { override.files.push('*.vue'); } } } return json; }); } }