UNPKG

@o3r/eslint-plugin

Version:

The module provides in-house eslint plugins to use in your own eslint configuration.

153 lines (152 loc) • 8.65 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = require("node:path"); const semver = require("semver"); const utils_1 = require("../../utils"); const utils_2 = require("../utils"); const version_harmonize_1 = require("./version-harmonize"); const defaultOptions = [{ ignoredDependencies: [], dependencyTypes: ['optionalDependencies', 'dependencies', 'devDependencies', 'peerDependencies', 'generatorDependencies'], alignPeerDependencies: false, alignResolutions: true, alignEngines: false, ignoredPackages: [] }]; const resolutionsFields = ['resolutions', 'overrides']; const enginesField = 'engines'; exports.default = (0, utils_1.createRule)({ name: 'json-dependency-versions-harmonize', meta: { hasSuggestions: true, type: 'problem', docs: { description: 'Ensure that the package dependency versions are aligned with the other packages of the workspace.' }, schema: [ { type: 'object', properties: { alignPeerDependencies: { type: 'boolean', description: 'Enforce to align the version of the dependencies with the latest range.' }, dependencyTypes: { type: 'array', description: 'List of dependency types to update', default: defaultOptions[0].dependencyTypes, items: { type: 'string' } }, ignoredDependencies: { type: 'array', description: 'List of dependencies to ignore', items: { type: 'string' } }, ignoredPackages: { type: 'array', description: 'List of package name to ignore when determining the dependencies versions', items: { type: 'string' } }, alignResolutions: { type: 'boolean', description: 'Align the resolutions dependencies with the latest determined range.' }, alignEngines: { type: 'boolean', description: 'Align the engines constraints with the latest determined range.' } }, additionalProperties: false } ], messages: { versionUpdate: 'Set version {{version}}', error: '{{depName}} should be updated to version {{version}} (from: {{packageJsonFile}})' }, fixable: 'code' }, defaultOptions, create: (context, [options]) => { const parserServices = (0, utils_2.getJsoncParserServices)(context); const dirname = path.dirname(context.filename); const workspace = (0, version_harmonize_1.findWorkspacePackageJsons)(dirname); const dependencyTypesWithInterest = [...options.dependencyTypes, ...(options.alignEngines ? [enginesField] : [])]; const bestRanges = workspace && (0, version_harmonize_1.getBestRanges)(dependencyTypesWithInterest, workspace.packages.filter(({ content }) => !content.name || !options.ignoredPackages.includes(content.name))); const ignoredDependencies = options.ignoredDependencies.map((dep) => new RegExp(dep.replace(/[$()+.?[\\\]^{|}]/g, '\\$&').replace(/\*/g, '.*'))); const dependencyTypes = [...dependencyTypesWithInterest, ...(options.alignResolutions ? resolutionsFields : [])]; if (parserServices.isJSON) { const rule = (node) => { if (node.expression.type === 'JSONObjectExpression') { const deps = node.expression.properties .filter(({ key }) => dependencyTypes.includes(key.type === 'JSONLiteral' ? key.value.toString() : key.name)); if (deps.length > 0 && bestRanges) { deps .map((depGroup) => depGroup.value) .filter((depGroup) => depGroup.type === 'JSONObjectExpression') .forEach((depGroup) => { const report = (name, resolvedName, dep, range, bestRange) => { if (bestRange && bestRange !== range && !range?.startsWith('workspace:')) { if (!options.alignPeerDependencies && depGroup.parent.type === 'JSONProperty' && range && (depGroup.parent.key.type === 'JSONLiteral' ? depGroup.parent.key.value.toString() : depGroup.parent.key.name) === 'peerDependencies' && semver.subset(bestRange, range, { includePrerelease: true })) { return; } context.report({ loc: dep.value.loc, messageId: 'error', data: { depName: name, version: bestRange, packageJsonFile: bestRanges[resolvedName].path }, fix: (fixer) => fixer.replaceTextRange(dep.value.range, `"${bestRange}"`), suggest: [ { messageId: 'versionUpdate', data: { version: bestRange }, fix: (fixer) => fixer.replaceTextRange(dep.value.range, `"${bestRange}"`) } ] }); } }; depGroup.properties.forEach((dependencyNode) => { const isResolutionsField = options.alignResolutions && depGroup.parent.type === 'JSONProperty' && resolutionsFields.includes(depGroup.parent.key.type === 'JSONLiteral' ? depGroup.parent.key.value.toString() : depGroup.parent.key.name); const getNodeDetails = (dep) => { const name = dep.key.type === 'JSONLiteral' ? dep.key.value.toString() : dep.key.name; const nameParts = name.split('/'); if (ignoredDependencies.some((ignore) => ignore.test(name))) { return; } const range = dep.value.type === 'JSONLiteral' ? dep.value.value : (dep.value.type === 'JSONIdentifier' ? dep.value.name : undefined); if (!range && dep.value.type === 'JSONObjectExpression') { return dep.value.properties .forEach((prop) => getNodeDetails(prop)); } const resolutionSubNameIndex = isResolutionsField ? nameParts.findIndex((_, i) => !!bestRanges[nameParts.slice(nameParts.length - i).join('/')]) : -1; const resolvedName = resolutionSubNameIndex > -1 ? nameParts.slice(nameParts.length - resolutionSubNameIndex).join('/') : name; const bestRange = (0, version_harmonize_1.getBestRange)(range, bestRanges[resolvedName]?.range); report(name, resolvedName, dep, range, bestRange); }; getNodeDetails(dependencyNode); }); }); } } }; return { JSONExpressionStatement: rule }; } return {}; } });