diff --git a/packages/@o3r/core/schematics/ng-add/utils/linter/index.ts b/packages/@o3r/core/schematics/ng-add/utils/linter/index.ts index 25773bd708..91b6f6e90a 100644 --- a/packages/@o3r/core/schematics/ng-add/utils/linter/index.ts +++ b/packages/@o3r/core/schematics/ng-add/utils/linter/index.ts @@ -13,12 +13,12 @@ export const shouldOtterLinterBeInstalled = async (context: SchematicContext): P try { require.resolve(`${linterPackageName}/package.json`); if (context.interactive) { - useOtterLinter = await askConfirmation(`You already have eslint installed. Would you like to add otter config rules for eslint? + useOtterLinter = await askConfirmation(`You already have ESLint installed. Would you like to add otter config rules for ESLint? Otherwise, you can add them later via this command: ng add @o3r/eslint-config-otter`, true); } } catch { - context.logger.info(`eslint package not installed. Skipping otter linter phase! -You can add otter linter config rules later to the project via this command: ng add @o3r/eslint-config-otter`); + context.logger.info(`ESLint package not installed. Skipping Otter linter phase! +You can add Otter linter config rules later to the project via this command: ng add @o3r/eslint-config-otter`); } return useOtterLinter; diff --git a/packages/@o3r/eslint-config-otter/.eslintrc.js b/packages/@o3r/eslint-config-otter/.eslintrc.js index 385c614aeb..7121d0352b 100644 --- a/packages/@o3r/eslint-config-otter/.eslintrc.js +++ b/packages/@o3r/eslint-config-otter/.eslintrc.js @@ -8,7 +8,8 @@ module.exports = { 'sourceType': 'module', 'project': [ 'tsconfig.builders.json', - 'tsconfig.eslint.json' + 'tsconfig.eslint.json', + 'tsconfig.spec.json' ] }, 'extends': [ diff --git a/packages/@o3r/eslint-config-otter/package.json b/packages/@o3r/eslint-config-otter/package.json index 2bf603f3df..31d052185f 100644 --- a/packages/@o3r/eslint-config-otter/package.json +++ b/packages/@o3r/eslint-config-otter/package.json @@ -82,6 +82,7 @@ "@o3r/build-helpers": "workspace:^", "@o3r/eslint-plugin": "workspace:^", "@o3r/schematics": "workspace:^", + "@o3r/test-helpers": "workspace:^", "@schematics/angular": "~17.3.0", "@stylistic/eslint-plugin-ts": "^1.5.4", "@types/jest": "~29.5.2", diff --git a/packages/@o3r/eslint-config-otter/project.json b/packages/@o3r/eslint-config-otter/project.json index 4c9ecd7219..c091cacaca 100644 --- a/packages/@o3r/eslint-config-otter/project.json +++ b/packages/@o3r/eslint-config-otter/project.json @@ -18,6 +18,12 @@ "jestConfig": "packages/@o3r/eslint-config-otter/jest.config.js" } }, + "test-int": { + "executor": "@nx/jest:jest", + "options": { + "jestConfig": "packages/@o3r//eslint-config-otter/testing/jest.config.it.js" + } + }, "lint": { "options": { "eslintConfig": "packages/@o3r/eslint-config-otter/.eslintrc.js", diff --git a/packages/@o3r/eslint-config-otter/schematics/index.it.spec.ts b/packages/@o3r/eslint-config-otter/schematics/index.it.spec.ts new file mode 100644 index 0000000000..7782275059 --- /dev/null +++ b/packages/@o3r/eslint-config-otter/schematics/index.it.spec.ts @@ -0,0 +1,25 @@ +/** + * Test environment exported by O3rEnvironment, must be first line of the file + * @jest-environment @o3r/test-helpers/jest-environment + * @jest-environment-o3r-app-folder test-app-eslint-config + */ +const o3rEnvironment = globalThis.o3rEnvironment; + +import { + getDefaultExecSyncOptions, + packageManagerExec, + packageManagerInstall, + packageManagerRunOnProject +} from '@o3r/test-helpers'; + +describe('new otter application with eslint-config', () => { + test('should add eslint-config to existing application', () => { + const { workspacePath, projectName, isInWorkspace, o3rVersion } = o3rEnvironment.testEnvironment; + const execAppOptions = {...getDefaultExecSyncOptions(), cwd: workspacePath}; + packageManagerExec({script: 'ng', args: ['add', `@o3r/eslint-config-otter@${o3rVersion}`, '--skip-confirmation', '--project-name', projectName]}, execAppOptions); + + expect(() => packageManagerInstall(execAppOptions)).not.toThrow(); + expect(() => packageManagerRunOnProject(projectName, isInWorkspace, {script: 'build'}, execAppOptions)).not.toThrow(); + expect(() => packageManagerExec({script: 'ng', args: ['lint', projectName]}, execAppOptions)).not.toThrow('Invalid lint configuration. Nothing to lint.'); + }); +}); diff --git a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/index.spec.ts b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/index.spec.ts index f64d43d008..67fb6a0372 100644 --- a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/index.spec.ts +++ b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/index.spec.ts @@ -22,7 +22,15 @@ describe('Generate linter files', () => { expect(tree.exists('.eslintignore')).toBe(true); expect(tree.exists('.eslintrc.js')).toBe(true); - expect(tree.exists('tsconfig.eslint.json')).toBe(true); + }); + + it('should not overwrite eslintignore but extend @o3r/eslint-config-otter', async () => { + initialTree.create('.eslintignore', 'I am inevitable'); + initialTree.create('.eslintrc.json', '{}'); + const tree = await lastValueFrom(callRule(updateLinterConfigs({}, path.join(__dirname, '..')), initialTree, context)); + + expect(tree.readText('.eslintignore')).toBe('I am inevitable'); + expect(tree.readJson('.eslintrc.json')).toEqual({'extends': ['@o3r/eslint-config-otter']}); }); it('should not overwrite linter files', async () => { diff --git a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/index.ts b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/index.ts index 9a9689858f..584bd39acc 100644 --- a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/index.ts +++ b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/index.ts @@ -1,6 +1,6 @@ -import { apply, chain, MergeStrategy, mergeWith, renameTemplateFiles, Rule, SchematicContext, template, Tree, url } from '@angular-devkit/schematics'; +import { apply, chain, MergeStrategy, mergeWith, move, renameTemplateFiles, Rule, SchematicContext, template, Tree, url } from '@angular-devkit/schematics'; +import { posix } from 'node:path'; -const tsEslintParserDep = '@typescript-eslint/parser'; /** * Add or update the Linter configuration * @param options @see RuleFactory.options @@ -16,34 +16,77 @@ export function updateLinterConfigs(options: { projectName?: string | null | und */ const updateTslintExtend: Rule = async (tree: Tree, context: SchematicContext) => { const eslintFilePath = '/.eslintrc.json'; - const eslintExists = tree.exists(eslintFilePath); - if (eslintExists) { + if (tree.exists(eslintFilePath)) { const eslintFile = tree.readJson(eslintFilePath) as { extends?: string | string[] }; eslintFile.extends = eslintFile.extends ? (eslintFile.extends instanceof Array ? eslintFile.extends : [eslintFile.extends]) : []; - if (eslintFile.extends.indexOf(tsEslintParserDep) === -1) { - eslintFile.extends.push(tsEslintParserDep); + const eslintConfigOtter = '@o3r/eslint-config-otter'; + if (eslintFile.extends.indexOf(eslintConfigOtter) === -1) { + eslintFile.extends.push(eslintConfigOtter); } tree.overwrite(eslintFilePath, JSON.stringify(eslintFile, null, 2)); } else { const { getAllFilesInTree, getTemplateFolder } = await import('@o3r/schematics'); - const eslintConfigFiles = getAllFilesInTree(tree, '/', ['**/.eslintrc.json'], false).filter((file) => /\.eslintrc/i.test(file)); + const eslintConfigFiles = getAllFilesInTree(tree, '/', ['**/.eslintrc.js'], false).filter((file) => /\.eslintrc/i.test(file)); if (!eslintConfigFiles.length) { - return mergeWith(apply(url(getTemplateFolder(rootPath, __dirname)), [ + return mergeWith(apply(url(getTemplateFolder(rootPath, __dirname, 'templates/workspace')), [ template({ dot: '.' }), renameTemplateFiles() ]), MergeStrategy.Overwrite); } else { - context.logger.warn('An unsupported format EsLint configuration already exists, an automatic update cannot be applied.'); - context.logger.warn(`You can manually extends "@o3r/eslint-config-otter" in your configuration ${eslintConfigFiles.map((f) => `"${f}"`).join(', ')}`); + context.logger.warn('An unsupported format ESLint configuration already exists, an automatic update cannot be applied.'); + context.logger.warn(`You can manually extend "@o3r/eslint-config-otter" in your configuration ${eslintConfigFiles.map((f) => `"${f}"`).join(', ')}`); + return; } } }; + /** + * Create linter files for the project + * @param tree + * @param context + */ + const createProjectFiles: Rule = async (tree: Tree, context: SchematicContext) => { + if (!options.projectName) { + return; + } + const { getWorkspaceConfig } = await import('@o3r/schematics'); + const workspace = getWorkspaceConfig(tree); + if (!workspace) { + return; + } + const workspaceProject = workspace.projects[options.projectName]; + + if (!workspaceProject) { + context.logger.warn('No project detected, linter task can not be added.'); + return; + } + + const projectRoot = workspaceProject.root; + const projectType = workspaceProject.projectType; + const eslintFilePath = posix.join(projectRoot, '/.eslintrc.js'); + + if (tree.exists(eslintFilePath)) { + context.logger.info(`${eslintFilePath} already exists.`); + return; + } else { + const { getTemplateFolder } = await import('@o3r/schematics'); + const rootRelativePath = posix.relative(projectRoot, tree.root.path.replace(/^\//, './')); + return mergeWith(apply(url(getTemplateFolder(rootPath, __dirname, 'templates/project')), [ + template({ + dot: '.', + projectTsConfig: projectType === 'application' ? 'tsconfig.app' : 'tsconfig.lib', + rootRelativePath + }), + move(projectRoot), + renameTemplateFiles() + ]), MergeStrategy.Overwrite); + } + }; /** * Update angular.json file to use ESLint builder. * @param tree @@ -52,10 +95,13 @@ export function updateLinterConfigs(options: { projectName?: string | null | und const editAngularJson: Rule = async (tree: Tree, context: SchematicContext) => { const { getWorkspaceConfig } = await import('@o3r/schematics'); const workspace = getWorkspaceConfig(tree); - const workspaceProject = options.projectName ? workspace?.projects[options.projectName] : undefined; + if (!workspace) { + return; + } + const workspaceProject = options.projectName ? workspace.projects[options.projectName] : undefined; - if (!workspace || !workspaceProject) { - context.logger.warn('No project detected, linter task can not be added'); + if (!workspaceProject) { + context.logger.warn('No project detected, linter task can not be added.'); return; } @@ -63,9 +109,9 @@ export function updateLinterConfigs(options: { projectName?: string | null | und workspaceProject.architect.lint ||= { builder: '@angular-eslint/builder:lint', options: { - eslintConfig: './.eslintrc.js', + eslintConfig: `${workspaceProject.root}/.eslintrc.js`, lintFilePatterns: [ - 'src/**/*.ts' + `${workspaceProject.sourceRoot || posix.join(workspaceProject.root, 'src') }/**/*.ts` ] } }; @@ -76,6 +122,7 @@ export function updateLinterConfigs(options: { projectName?: string | null | und return chain([ updateTslintExtend, + createProjectFiles, editAngularJson ]); } diff --git a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/__dot__eslintignore.template b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/__dot__eslintignore.template deleted file mode 100644 index 22773301d4..0000000000 --- a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/__dot__eslintignore.template +++ /dev/null @@ -1,4 +0,0 @@ -/node_modules -/dist* -/test -/tmp \ No newline at end of file diff --git a/packages/@o3r/workspace/schematics/library/templates/ng/.eslintrc.js.template b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/project/__dot__eslintrc.js.template similarity index 55% rename from packages/@o3r/workspace/schematics/library/templates/ng/.eslintrc.js.template rename to packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/project/__dot__eslintrc.js.template index 09602be21e..254b4570d0 100644 --- a/packages/@o3r/workspace/schematics/library/templates/ng/.eslintrc.js.template +++ b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/project/__dot__eslintrc.js.template @@ -1,19 +1,15 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable quote-props */ - module.exports = { 'root': true, 'parserOptions': { 'tsconfigRootDir': __dirname, 'project': [ - 'tsconfig.build.json', + '<%= projectTsConfig %>.json', 'tsconfig.spec.json', - 'tsconfig.builders.json', 'tsconfig.eslint.json' ], 'sourceType': 'module' }, 'extends': [ - '<%= eslintRcPath %>' + '<%= rootRelativePath %>/.eslintrc.js' ] }; diff --git a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/project/tsconfig.eslint.json b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/project/tsconfig.eslint.json new file mode 100644 index 0000000000..b3b38b3bc8 --- /dev/null +++ b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/project/tsconfig.eslint.json @@ -0,0 +1,6 @@ +{ + "extends": "./<%= projectTsConfig %>", + "include": [ + ".eslintrc.js" + ] +} diff --git a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/tsconfig.eslint.json b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/tsconfig.eslint.json deleted file mode 100644 index b512fc095a..0000000000 --- a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/tsconfig.eslint.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.app", - "include": [ - ".eslintrc.js", - "jest.config.js", - "src/**/*.ts", - "e2e-playwright/**/*.ts" - ] -} diff --git a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/workspace/__dot__eslintignore.template b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/workspace/__dot__eslintignore.template new file mode 100644 index 0000000000..002d9543da --- /dev/null +++ b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/workspace/__dot__eslintignore.template @@ -0,0 +1,5 @@ +/node_modules +**/dist +**/dist-* +**/test +**/tmp diff --git a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/__dot__eslintrc.js.template b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/workspace/__dot__eslintrc.js.template similarity index 95% rename from packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/__dot__eslintrc.js.template rename to packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/workspace/__dot__eslintrc.js.template index 086d822884..e608cd1f66 100644 --- a/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/__dot__eslintrc.js.template +++ b/packages/@o3r/eslint-config-otter/schematics/ng-add/linter/templates/workspace/__dot__eslintrc.js.template @@ -5,9 +5,6 @@ module.exports = { 'parser': require.resolve('@typescript-eslint/parser'), 'parserOptions': { 'tsconfigRootDir': __dirname, - 'project': [ - 'tsconfig.eslint.json' - ], 'ecmaVersion': 12 }, 'env': { diff --git a/packages/@o3r/eslint-config-otter/testing/jest.config.it.js b/packages/@o3r/eslint-config-otter/testing/jest.config.it.js new file mode 100644 index 0000000000..3f0781b8f0 --- /dev/null +++ b/packages/@o3r/eslint-config-otter/testing/jest.config.it.js @@ -0,0 +1,8 @@ +const { join } = require('node:path'); +const getJestConfig = require('../../../../jest.config.it').getJestConfig; + +/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */ +module.exports = { + ...getJestConfig(join(__dirname, '..')), + displayName: require('../package.json').name +}; diff --git a/packages/@o3r/eslint-plugin/tsconfig.spec.json b/packages/@o3r/eslint-plugin/tsconfig.spec.json index 96e201f0d3..5fec609350 100644 --- a/packages/@o3r/eslint-plugin/tsconfig.spec.json +++ b/packages/@o3r/eslint-plugin/tsconfig.spec.json @@ -6,7 +6,7 @@ } ], "include": [ - "./src/**/*.spec.ts", + "**/*.spec.ts" ], "exclude": [] } diff --git a/packages/@o3r/schematics/src/utility/monorepo.ts b/packages/@o3r/schematics/src/utility/monorepo.ts index cb6316acb5..39844404a3 100644 --- a/packages/@o3r/schematics/src/utility/monorepo.ts +++ b/packages/@o3r/schematics/src/utility/monorepo.ts @@ -8,7 +8,7 @@ import { getWorkspaceConfig } from './loaders'; /** * Find the relative path to a configuration file at the monorepo root * @param tree - * @param files List of files to look for, the first of the list will used + * @param files List of files to look for, the first of the list will be used * @param originPath Path from where to calculate the relative path * @returns */ diff --git a/packages/@o3r/workspace/schematics/library/rules/rules.ng.ts b/packages/@o3r/workspace/schematics/library/rules/rules.ng.ts index 81ba60f3e1..cfd8150fa7 100644 --- a/packages/@o3r/workspace/schematics/library/rules/rules.ng.ts +++ b/packages/@o3r/workspace/schematics/library/rules/rules.ng.ts @@ -23,18 +23,16 @@ export function ngGenerateModule(options: NgGenerateModuleSchema & { targetPath: */ const updateNgTemplate: Rule = (tree, context) => { const o3rCorePackageJsonPath = path.resolve(__dirname, '..', '..', '..', 'package.json'); - const o3rCorePackageJson: PackageJson & { generatorDependencies?: Record } = JSON.parse(readFileSync(o3rCorePackageJsonPath)!.toString()); + const o3rCorePackageJson: PackageJson & { generatorDependencies?: Record } = JSON.parse(readFileSync(o3rCorePackageJsonPath).toString()); const otterVersion = o3rCorePackageJson.dependencies!['@o3r/schematics']; const templateNg = apply(url('./templates/ng'), [ template({ ...options, - runner: process.env.npm_execpath && /[\\/][^\\/]yarn[^\\/]js$/.test(process.env.npm_execpath) ? 'yarn run' : 'npm run', tsconfigSpecPath: findConfigFileRelativePath(tree, ['tsconfig.test.json', 'tsconfig.spec.json', 'tsconfig.jest.json', 'tsconfig.jasmine.json', 'tsconfig.base.json', 'tsconfig.json'], options.targetPath), tsconfigBasePath: findConfigFileRelativePath(tree, ['tsconfig.base.json', 'tsconfig.json'], options.targetPath), - tsconfigBuildPath: findConfigFileRelativePath(tree, ['tsconfig.build.json', 'tsconfig.base.json', 'tsconfig.json'], options.targetPath), - eslintRcPath: findConfigFileRelativePath(tree, ['.eslintrc.json', '.eslintrc.js'], options.targetPath) + tsconfigBuildPath: findConfigFileRelativePath(tree, ['tsconfig.build.json', 'tsconfig.base.json', 'tsconfig.json'], options.targetPath) }), renameTemplateFiles(), move(options.targetPath) diff --git a/packages/@o3r/workspace/schematics/library/templates/ng/tsconfig.eslint.json.template b/packages/@o3r/workspace/schematics/library/templates/ng/tsconfig.eslint.json.template deleted file mode 100644 index 7082e8cc43..0000000000 --- a/packages/@o3r/workspace/schematics/library/templates/ng/tsconfig.eslint.json.template +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./tsconfig.lib", - "include": [ - ".eslintrc.js", - "jest.config.js", - "testing/*" - ] -} diff --git a/packages/@o3r/workspace/schematics/ng-add/helpers/linter.ts b/packages/@o3r/workspace/schematics/ng-add/helpers/linter.ts index 3b0b36db88..40a6141c51 100644 --- a/packages/@o3r/workspace/schematics/ng-add/helpers/linter.ts +++ b/packages/@o3r/workspace/schematics/ng-add/helpers/linter.ts @@ -12,12 +12,12 @@ export const shouldOtterLinterBeInstalled = async (context: SchematicContext): P try { require.resolve(`${linterPackageName}/package.json`); if (context.interactive) { - useOtterLinter = await askConfirmation(`You already have eslint installed. Would you like to add otter config rules for eslint? + useOtterLinter = await askConfirmation(`You already have ESLint installed. Would you like to add Otter config rules for ESLint? Otherwise, you can add them later via this command: ng add @o3r/eslint-config-otter`, true); } } catch { - context.logger.info(`eslint package not installed. Skipping otter linter phase! -You can add otter linter config rules later to the project via this command: ng add @o3r/eslint-config-otter`); + context.logger.info(`ESLint package not installed. Skipping otter linter phase! +You can add Otter linter config rules later to the project via this command: ng add @o3r/eslint-config-otter`); } return useOtterLinter; diff --git a/packages/@o3r/workspace/schematics/rule-factories/project-configs.ts b/packages/@o3r/workspace/schematics/rule-factories/project-configs.ts index 5b08f21be0..a2f65fc012 100644 --- a/packages/@o3r/workspace/schematics/rule-factories/project-configs.ts +++ b/packages/@o3r/workspace/schematics/rule-factories/project-configs.ts @@ -37,6 +37,9 @@ export function updateProjectTsConfig(targetPath: string, tsconfigName: string, tsconfig.config.compilerOptions ||= {}; tsconfig.config.compilerOptions.outDir = './dist'; + delete tsconfig.config.files; + tsconfig.config.include = ['./src/**/*.ts']; + tsconfig.config.exclude = ['**/*.spec.ts', '**/*.jest.ts']; tree.overwrite(tsconfigPath, JSON.stringify(tsconfig.config, null, 2)); return tree; }; diff --git a/yarn.lock b/yarn.lock index 660a729dbe..4a130a8ea1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7492,6 +7492,7 @@ __metadata: "@o3r/build-helpers": "workspace:^" "@o3r/eslint-plugin": "workspace:^" "@o3r/schematics": "workspace:^" + "@o3r/test-helpers": "workspace:^" "@schematics/angular": "npm:~17.3.0" "@stylistic/eslint-plugin-ts": "npm:^1.5.4" "@types/jest": "npm:~29.5.2"