Skip to content

Commit

Permalink
feat(eslint-plugin): renamed exported recommended (#2354)
Browse files Browse the repository at this point in the history
## Proposed change

feat(eslint-plugin): renamed exported recommended
fix(eslint-plugin): turn yaml parser really optional
deprecate(eslint-plugin): recommended exports based on types

<!-- Please include a summary of the changes and the related issue.
Please also include relevant motivation and context. List any
dependencies that is required for this change. -->

## Related issues

- 🚀 Feature resolves #1487
- 🐛 Fix resolves #2482

<!-- Please make sure to follow the contributing guidelines on
https://github.com/amadeus-digital/Otter/blob/main/CONTRIBUTING.md -->
  • Loading branch information
kpanot authored Dec 4, 2024
2 parents f43e304 + b600751 commit 9df67aa
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 44 deletions.
5 changes: 5 additions & 0 deletions packages/@o3r/eslint-config-otter/migration.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
"version": "10.0.0-alpha.0",
"description": "Updates of @o3r/eslint-config-otter to v10.0.*",
"factory": "./schematics/ng-update/index#updateV100"
},
"migration-v11_6": {
"version": "11.6.0-prerelease.0",
"description": "Updates of @o3r/eslint-config-otter to v11.6.*",
"factory": "./schematics/ng-update/index#updateV116"
}
}
}
21 changes: 21 additions & 0 deletions packages/@o3r/eslint-config-otter/schematics/ng-update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import {
import {
addStylistic,
} from './v10.0/stylistic';
import {
updateEslintRecommended,
} from './v11.6/update-configs/update-configs';

/**
* Update of Otter library V10.0
Expand All @@ -22,7 +25,25 @@ function updateV100fn(): Rule {
};
}

/**
* Update of Otter library V11.6
*/
function updateV116Fn(): Rule {
return (tree, context) => {
const updateRules: Rule[] = [
updateEslintRecommended()
];

return chain(updateRules)(tree, context);
};
}

/**
* Update of Otter library V10.0
*/
export const updateV100 = createSchematicWithMetricsIfInstalled(updateV100fn);

/**
* Update of Otter library V11.6
*/
export const updateV116 = createSchematicWithMetricsIfInstalled(updateV116Fn);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
Tree,
} from '@angular-devkit/schematics';
import {
updateEslintRecommended,
} from './update-configs';

let findFilesInTreeFn: () => any[] = () => [];

jest.mock('@o3r/schematics', () => ({
findFilesInTree: jest.fn().mockImplementation(() => findFilesInTreeFn())
}));

describe('updateEslintRecommended', () => {
beforeEach(() => jest.restoreAllMocks());

it('should update configs', async () => {
const initialTree = Tree.empty();
initialTree.create('random.file', 'a file containing json-recommended');
initialTree.create('/test/.eslintrc.json', '{ "extends": ["@o3r/json-recommended", "@other/json-recommended"] }');
findFilesInTreeFn = () => [
initialTree.get('random.file'),
initialTree.get('/test/.eslintrc.json')
];
await updateEslintRecommended()(initialTree, null as any);

expect(initialTree.readText('random.file')).toContain('json-recommended');
expect(initialTree.readText('/test/.eslintrc.json')).toBe('{ "extends": ["@o3r/monorepo-recommended", "@other/json-recommended"] }');
});

it('should update configs on findFilesInTree failure', async () => {
findFilesInTreeFn = () => {
throw new Error('test');
};
const initialTree = Tree.empty();
initialTree.create('random.file', 'a file containing json-recommended');
initialTree.create('/test/.eslintrc.json', '{ "extends": ["@o3r/json-recommended", "@other/json-recommended"] }');
await updateEslintRecommended()(initialTree, null as any);

expect(initialTree.readText('random.file')).toContain('json-recommended');
expect(initialTree.readText('/test/.eslintrc.json')).toBe('{ "extends": ["@o3r/monorepo-recommended", "@other/json-recommended"] }');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
basename,
} from 'node:path';
import type {
FileEntry,
Rule,
} from '@angular-devkit/schematics';

/**
* Update Eslint files to new recommended names
*/
export function updateEslintRecommended(): Rule {
const configFilePattern = /^.?eslint(:?rc)?\..*/;
const toReplace = {

'angular-template-recommended': /(['"](?:plugin:)?@o3r\/)template-recommended(['"])/g,

'monorepo-recommended': /(['"](?:plugin:)?@o3r\/)json-recommended(['"])/g
};

const isFileToParse = (path: string) => configFilePattern.test(basename(path));

return async (tree) => {
let files: FileEntry[];
try {
const { findFilesInTree } = await import('@o3r/schematics');
files = findFilesInTree(tree.getDir('/'), isFileToParse);
} catch {
files = [];
tree.visit((path) => isFileToParse(path) && files.push(tree.get(path)!));
}

files.forEach(({ content, path }) => {
const contentStr = content.toString();
const newContent = Object.entries(toReplace).reduce((acc, [rule, regexp]) => {
return acc.replaceAll(regexp, `$1${rule}$2`);
}, contentStr);

if (contentStr !== newContent) {
tree.overwrite(path, newContent);
}
});
};
}
15 changes: 15 additions & 0 deletions packages/@o3r/eslint-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,34 @@ module.exports = {
}
},

// deprecated: should use `angular-template-recommended` instead. Will be removed in v13.
'template-recommended': {
rules: {
'@o3r/no-folder-import-for-module': 'error',
'@o3r/template-async-number-limitation': 'warn'
}
},

// deprecated: should use `monorepo-recommended` instead. Will be removed in v13.
'json-recommended': {
rules: {
'@o3r/json-dependency-versions-harmonize': 'error'
}
},

'angular-template-recommended': {
rules: {
'@o3r/no-inner-html': 'off',
'@o3r/template-async-number-limitation': 'error'
}
},

'monorepo-recommended': {
rules: {
'@o3r/json-dependency-versions-harmonize': 'error'
}
},

'yarn-recommended': {
rules: {
'@o3r/yarnrc-package-extensions-harmonize': 'error'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,18 @@ beforeAll(async () => {
await fs.writeFile(path.join(fakeFolder, 'local', 'packages', 'my-package-2', 'package.json'), JSON.stringify(packageJson2));
});

ruleTester.run('json-dependency-versions-harmonize', yamlDependencyVersionsHarmonize, {
ruleTester.run('yarnrc-dependency-versions-harmonize', yamlDependencyVersionsHarmonize, {
valid: [
{ code: bestVersionYaml, filename: packageToLint }
{ code: bestVersionYaml, filename: packageToLint },
{
filename: packageToLint,
code: yamlToUpdate,
options: [{
ignoredDependencies: ['myDep']
}]
},
{ filename: packageToLint, code: '' },
{ filename: 'not/a/yaml.txt', code: 'not a yaml' }
],
invalid: [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as path from 'node:path';
import * as semver from 'semver';
import {
type AST,
getStaticYAMLValue,
import type {
AST,
} from 'yaml-eslint-parser';
import {
findWorkspacePackageJsons,
Expand All @@ -29,6 +28,14 @@ export interface YarnrcPackageExtensionsHarmonizeOptions {
yarnrcDependencyTypes: string[];
}

const isYAMLScalar = (node: AST.YAMLWithMeta | AST.YAMLContent | null): node is AST.YAMLScalar => node?.type === 'YAMLScalar';

const getStaticYAMLValue = (node: AST.YAMLWithMeta | AST.YAMLContent | null) => {
return isYAMLScalar(node)
? node.value?.toString()
: undefined;
};

const defaultOptions: [YarnrcPackageExtensionsHarmonizeOptions] = [{
ignoredDependencies: [],
excludePackages: [],
Expand Down Expand Up @@ -99,49 +106,48 @@ export default createRule<[YarnrcPackageExtensionsHarmonizeOptions, ...any], 've
const ignoredDependencies = options.ignoredDependencies.map((dep) => new RegExp(dep.replace(/[$()+.?[\\\]^{|}]/g, '\\$&').replace(/\*/g, '.*')));

if (parserServices.isYAML) {
const rule = (node: AST.YAMLPair) => {
if (node.value) {
const range = getStaticYAMLValue(node.value)?.toString();
const parent = node.parent.parent && node.parent.parent.type === 'YAMLPair' && getStaticYAMLValue(node.parent.parent.key!)?.toString();
const baseNode = node.parent.parent.parent.parent?.parent?.parent;
const isCorrectNode = baseNode && baseNode.type === 'YAMLPair' && getStaticYAMLValue(baseNode.key!)?.toString() === 'packageExtensions';
if (isCorrectNode && semver.validRange(range) && parent && options.yarnrcDependencyTypes.includes(parent)) {
const depName = node.key && getStaticYAMLValue(node.key)?.toString();
if (!depName || !bestRanges[depName] || ignoredDependencies.some((ignore) => ignore.test(depName))) {
return;
}
const minYarnrcVersion = semver.minVersion(range!);
const minBestRangeVersion = semver.minVersion(bestRanges[depName].range);
if (minYarnrcVersion && minBestRangeVersion && semver.lt(minYarnrcVersion, minBestRangeVersion)) {
const version = bestRanges[depName].range;
const packageJsonFile = bestRanges[depName].path;
context.report({
loc: node.value.loc,
messageId: 'error',
data: {
depName,
version,
packageJsonFile
},
fix: (fixer) => fixer.replaceTextRange(node.value!.range, `${version}`),
suggest: [
{
messageId: 'versionUpdate',
data: {
version
},
fix: (fixer) => fixer.replaceTextRange(node.value!.range, `${version}`)
}
]
});
return {
// eslint-disable-next-line @typescript-eslint/naming-convention -- name required by Yaml Eslint parser
YAMLPair: (node: AST.YAMLPair) => {
if (node.value) {
const range = getStaticYAMLValue(node.value)?.toString();
const parent = node.parent.parent && node.parent.parent.type === 'YAMLPair' && getStaticYAMLValue(node.parent.parent.key)?.toString();
const baseNode = node.parent.parent.parent.parent?.parent?.parent;
const isCorrectNode = baseNode && baseNode.type === 'YAMLPair' && getStaticYAMLValue(baseNode.key)?.toString() === 'packageExtensions';
if (isCorrectNode && semver.validRange(range) && parent && options.yarnrcDependencyTypes.includes(parent)) {
const depName = node.key && getStaticYAMLValue(node.key)?.toString();
if (!depName || !bestRanges[depName] || ignoredDependencies.some((ignore) => ignore.test(depName))) {
return;
}
const minYarnrcVersion = semver.minVersion(range!);
const minBestRangeVersion = semver.minVersion(bestRanges[depName].range);
if (minYarnrcVersion && minBestRangeVersion && semver.lt(minYarnrcVersion, minBestRangeVersion)) {
const version = bestRanges[depName].range;
const packageJsonFile = bestRanges[depName].path;
context.report({
loc: node.value.loc,
messageId: 'error',
data: {
depName,
version,
packageJsonFile
},
fix: (fixer) => fixer.replaceTextRange(node.value!.range, `${version}`),
suggest: [
{
messageId: 'versionUpdate',
data: {
version
},
fix: (fixer) => fixer.replaceTextRange(node.value!.range, `${version}`)
}
]
});
}
}
}
}
};

return {
YAMLPair: rule
};
}
return {};
}
Expand Down

0 comments on commit 9df67aa

Please sign in to comment.