@santi020k/eslint-config-basic 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/agent-skill-generator.d.ts +7 -18
  3. package/dist/agent-skill-generator.js +54 -18
  4. package/dist/agent-skill-generator.js.map +1 -0
  5. package/dist/cli-advanced.d.ts +7 -0
  6. package/dist/cli-advanced.js +74 -15
  7. package/dist/cli-advanced.js.map +1 -0
  8. package/dist/cli-migration.js +388 -34
  9. package/dist/cli-migration.js.map +1 -0
  10. package/dist/cli-package-manager.d.ts +10 -0
  11. package/dist/cli-package-manager.js +135 -0
  12. package/dist/cli-package-manager.js.map +1 -0
  13. package/dist/cli-preset.d.ts +103 -0
  14. package/dist/cli-preset.js +562 -0
  15. package/dist/cli-preset.js.map +1 -0
  16. package/dist/cli-workflows.js +1 -0
  17. package/dist/cli-workflows.js.map +1 -0
  18. package/dist/cli.d.ts +14 -2
  19. package/dist/cli.js +689 -94
  20. package/dist/cli.js.map +1 -0
  21. package/dist/define-config-metadata.d.ts +10 -0
  22. package/dist/define-config-metadata.js +16 -0
  23. package/dist/define-config-metadata.js.map +1 -0
  24. package/dist/frameworks.d.ts +1 -0
  25. package/dist/frameworks.js +6 -2
  26. package/dist/frameworks.js.map +1 -0
  27. package/dist/index.d.ts +10 -3
  28. package/dist/index.js +145 -27
  29. package/dist/index.js.map +1 -0
  30. package/dist/integrations.js +4 -1
  31. package/dist/integrations.js.map +1 -0
  32. package/dist/optional-package-errors.d.ts +3 -0
  33. package/dist/optional-package-errors.js +15 -0
  34. package/dist/optional-package-errors.js.map +1 -0
  35. package/dist/recommended.js +1 -0
  36. package/dist/recommended.js.map +1 -0
  37. package/dist/resolvers.js +1 -0
  38. package/dist/resolvers.js.map +1 -0
  39. package/dist/tailwind.d.ts +3 -1
  40. package/dist/tailwind.js +115 -13
  41. package/dist/tailwind.js.map +1 -0
  42. package/package.json +20 -19
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli-advanced.ts"],"sourcesContent":["/* eslint-disable no-console -- CLI handlers own user-facing terminal output */\n/* eslint-disable complexity -- report builders intentionally cover compatibility and config-source branches */\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { createRequire, findPackageJSON } from 'node:module'\nimport { basename, dirname, join } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\ninterface SemverApi {\n satisfies: (version: string, range: string) => boolean\n subset: (subRange: string, superRange: string) => boolean\n validRange: (range: string) => null | string\n}\n\ninterface PackageManifest {\n engines?: { node?: string }\n name?: string\n peerDependencies?: Record<string, string>\n version?: string\n}\n\ninterface ResolvedPackageManifest {\n manifest: PackageManifest\n path: string\n}\n\ninterface FlatConfigEntry {\n files?: unknown\n name?: string\n rules?: Record<string, unknown>\n}\n\ninterface ProjectEslint {\n calculateConfigForFile: (filePath: string) => Promise<unknown>\n lintFiles: (patterns: string[]) => Promise<LintResult[]>\n}\n\ninterface LintResult {\n filePath: string\n messages: {\n column?: number\n endColumn?: number\n endLine?: number\n line?: number\n message: string\n ruleId: null | string\n }[]\n}\n\ninterface RuleDiagnostic {\n column: null | number\n endColumn: null | number\n endLine: null | number\n file: string\n line: null | number\n message: string\n}\n\ntype ProjectEslintConstructor = new(options: { cwd: string }) => ProjectEslint\n\nexport interface ExplainRuleOptions {\n file?: string\n json?: boolean\n rule: string\n}\n\nconst CONFIG_FILENAMES = [\n 'eslint.config.js',\n 'eslint.config.mjs',\n 'eslint.config.cjs',\n 'eslint.config.ts',\n 'eslint.config.mts',\n 'eslint.config.cts'\n]\n\nconst BASIC_PACKAGE_NAME = '@santi020k/eslint-config-basic'\nconst FULL_PACKAGE_NAME = '@santi020k/eslint-config-full'\n\nconst RULE_PACKAGE_HINTS: Partial<Record<string, string>> = {\n '@angular-eslint': '@santi020k/eslint-config-angular',\n '@eslint-react': '@santi020k/eslint-config-react',\n '@next/next': '@santi020k/eslint-config-next',\n '@typescript-eslint': '@santi020k/eslint-config-typescript',\n astro: '@santi020k/eslint-config-astro',\n cypress: '@santi020k/eslint-config-testing',\n jest: '@santi020k/eslint-config-testing',\n jsonc: '@santi020k/eslint-config-formats',\n playwright: '@santi020k/eslint-config-testing',\n react: '@santi020k/eslint-config-react',\n 'testing-library': '@santi020k/eslint-config-testing',\n unicorn: '@santi020k/eslint-config-extensions',\n vue: '@santi020k/eslint-config-vue'\n}\n\nconst semver = createRequire(import.meta.url)('semver') as SemverApi\n\nconst readJson = (filePath: string): null | Record<string, unknown> => {\n if (!existsSync(filePath)) return null\n\n try {\n return JSON.parse(readFileSync(filePath, 'utf8')) as Record<string, unknown>\n } catch {\n return null\n }\n}\n\nconst findConfigPath = (cwd: string): null | string => (\n CONFIG_FILENAMES.map(file => join(cwd, file)).find(file => existsSync(file)) ?? null\n)\n\nconst flattenConfig = async (value: unknown): Promise<FlatConfigEntry[]> => {\n const resolved = await value\n\n if (Array.isArray(resolved)) {\n const nested = await Promise.all(resolved.map(entry => flattenConfig(entry)))\n\n return nested.flat()\n }\n\n if (resolved && typeof resolved === 'object') return [resolved]\n\n return []\n}\n\nconst loadConfigEntries = async (configPath: null | string): Promise<FlatConfigEntry[]> => {\n if (!configPath) return []\n\n try {\n const loaded: unknown = await import(`${pathToFileURL(configPath).href}?basic-eslint-explain=${Date.now()}`)\n\n if (!loaded || typeof loaded !== 'object' || !('default' in loaded)) return []\n\n return await flattenConfig((loaded).default)\n } catch {\n return []\n }\n}\n\nconst loadProjectEslint = (cwd: string): ProjectEslint => {\n const projectRequire = createRequire(join(cwd, 'package.json'))\n const eslintModule = projectRequire('eslint') as { ESLint?: ProjectEslintConstructor }\n\n if (!eslintModule.ESLint) throw new Error('The installed eslint package does not expose the ESLint API.')\n\n return new eslintModule.ESLint({ cwd })\n}\n\nconst getRuleSeverity = (ruleValue: unknown): unknown => (\n Array.isArray(ruleValue) ? ruleValue[0] : ruleValue\n)\n\nconst isRuleEnabled = (ruleValue: unknown): boolean => {\n const severity = getRuleSeverity(ruleValue)\n\n return severity !== 0 && severity !== 'off'\n}\n\nconst getPackageHint = (rule: string): null | string => {\n const prefix = rule.includes('/') ? rule.slice(0, rule.indexOf('/')) : rule\n const scopedPrefix = rule.startsWith('@') ? rule.split('/').slice(0, 2).join('/') : prefix\n\n return RULE_PACKAGE_HINTS[scopedPrefix] ?? RULE_PACKAGE_HINTS[prefix] ?? null\n}\n\nconst getRuleDiagnostics = async (\n eslint: ProjectEslint,\n file: string,\n rule: string\n): Promise<RuleDiagnostic[]> => {\n if (rule !== 'complexity') return []\n\n try {\n const results = await eslint.lintFiles([file])\n\n return results.flatMap(result => result.messages\n .filter(message => message.ruleId === rule)\n .map(message => ({\n column: message.column ?? null,\n endColumn: message.endColumn ?? null,\n endLine: message.endLine ?? null,\n file: result.filePath,\n line: message.line ?? null,\n message: message.message\n })))\n } catch {\n return []\n }\n}\n\nexport const handleExplainRule = async (\n cwd: string = process.cwd(),\n options: ExplainRuleOptions\n): Promise<void> => {\n const configPath = findConfigPath(cwd)\n const file = options.file ?? 'eslint.config.js'\n const eslint = loadProjectEslint(cwd)\n const effective = await eslint.calculateConfigForFile(file) as null | { rules?: Record<string, unknown> }\n const effectiveValue = effective?.rules?.[options.rule]\n const entries = await loadConfigEntries(configPath)\n const diagnostics = await getRuleDiagnostics(eslint, file, options.rule)\n\n const sources = entries.flatMap((entry, index) => {\n if (!entry.rules || !Object.hasOwn(entry.rules, options.rule)) return []\n\n return [{\n files: entry.files ?? null,\n index,\n name: entry.name ?? `anonymous config #${index + 1}`,\n value: entry.rules[options.rule]\n }]\n })\n\n const payload = {\n configFile: configPath ? basename(configPath) : null,\n diagnostics,\n effective: effectiveValue ?? null,\n enabled: effectiveValue !== undefined && isRuleEnabled(effectiveValue),\n file,\n packageHint: getPackageHint(options.rule),\n rule: options.rule,\n severity: effectiveValue === undefined ? null : getRuleSeverity(effectiveValue),\n sources\n }\n\n if (options.json) {\n console.log(JSON.stringify(payload, null, 2))\n } else {\n console.log([\n `ESLint rule explanation: ${payload.rule}`,\n `- File: ${payload.file}`,\n `- Config: ${payload.configFile ?? 'none'}`,\n `- Effective value: ${JSON.stringify(payload.effective)}`,\n `- Enabled: ${payload.enabled ? 'yes' : 'no'}`,\n `- Likely package: ${payload.packageHint ?? 'core or custom config'}`,\n `- Defining entries: ${payload.sources.length}`,\n ...(payload.diagnostics.length > 0 ?\n [`- Current findings: ${payload.diagnostics.length}`] :\n [])\n ].join('\\n'))\n\n for (const source of payload.sources) {\n console.log(` - ${source.name}: ${JSON.stringify(source.value)}`)\n }\n\n for (const diagnostic of payload.diagnostics) {\n console.log(\n ` - ${diagnostic.file}:${diagnostic.line ?? '?'}:${diagnostic.column ?? '?'} ` +\n diagnostic.message\n )\n }\n }\n\n if (effectiveValue === undefined) process.exitCode = 1\n}\n\nconst getDependencyVersions = (manifest: null | Record<string, unknown>): Record<string, string> => {\n const fields = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']\n const dependencies: Record<string, string> = {}\n\n for (const field of fields) {\n const values = manifest?.[field]\n\n if (!values || typeof values !== 'object' || Array.isArray(values)) continue\n\n for (const [name, version] of Object.entries(values)) {\n if (typeof version === 'string') dependencies[name] = version\n }\n }\n\n return dependencies\n}\n\nconst readResolvedManifest = (\n basePath: string,\n packageName: string\n): null | ResolvedPackageManifest => {\n try {\n const manifestPath = findPackageJSON(packageName, pathToFileURL(basePath))\n\n const manifest = manifestPath ?\n readJson(manifestPath) as null | PackageManifest :\n null\n\n if (manifestPath && manifest?.name === packageName) return { manifest, path: manifestPath }\n } catch {\n // Fall through for runtimes where package-JSON resolution is unavailable.\n }\n\n try {\n let directory = dirname(createRequire(basePath).resolve(packageName))\n\n for (let depth = 0; depth < 6; depth++) {\n const manifestPath = join(directory, 'package.json')\n const manifest = readJson(manifestPath) as null | PackageManifest\n\n if (manifest?.name === packageName) return { manifest, path: manifestPath }\n\n directory = dirname(directory)\n }\n } catch {\n return null\n }\n\n return null\n}\n\nconst readWorkspaceManifest = (cwd: string, packageName: string): null | ResolvedPackageManifest => {\n const packagesPath = join(cwd, 'packages')\n\n if (!existsSync(packagesPath)) return null\n\n for (const entry of readdirSync(packagesPath, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue\n\n const manifestPath = join(packagesPath, entry.name, 'package.json')\n const manifest = readJson(manifestPath) as null | PackageManifest\n\n if (manifest?.name === packageName) return { manifest, path: manifestPath }\n }\n\n return null\n}\n\nexport const createCompatibilityReport = (cwd: string = process.cwd()) => {\n const rootManifestPath = join(cwd, 'package.json')\n const rootManifest = readJson(rootManifestPath)\n const declared = getDependencyVersions(rootManifest)\n const consumerNodeRange = (rootManifest as null | PackageManifest)?.engines?.node ?? null\n\n const configPackages = Object.keys(declared)\n .filter(name => name.startsWith('@santi020k/eslint-config-'))\n .sort()\n\n const runtimeVersions = {\n consumerNodeRange,\n eslint: readResolvedManifest(rootManifestPath, 'eslint')?.manifest.version ?? null,\n node: process.versions.node,\n typescript: readResolvedManifest(rootManifestPath, 'typescript')?.manifest.version ?? null\n }\n\n const packages = configPackages.map(name => {\n const resolvedPackage = readResolvedManifest(rootManifestPath, name) ?? readWorkspaceManifest(cwd, name)\n const manifest = resolvedPackage?.manifest\n const issues: string[] = []\n\n const aggregatedBasic = name === FULL_PACKAGE_NAME && resolvedPackage ?\n readResolvedManifest(resolvedPackage.path, BASIC_PACKAGE_NAME) :\n null\n\n if (!manifest) {\n issues.push('declared but not installed')\n } else {\n const nodeRange = manifest.engines?.node\n\n if (nodeRange && !semver.satisfies(runtimeVersions.node, nodeRange)) {\n issues.push(`requires Node ${nodeRange}`)\n }\n\n if (nodeRange && consumerNodeRange) {\n if (!semver.validRange(consumerNodeRange)) {\n issues.push(`consumer engines.node ${consumerNodeRange} is not a valid semver range`)\n } else if (!semver.subset(consumerNodeRange, nodeRange)) {\n issues.push(\n `consumer engines.node ${consumerNodeRange} permits unsupported runtimes; requires ${nodeRange}`\n )\n }\n }\n\n for (const peer of ['eslint', 'typescript']) {\n const range = manifest.peerDependencies?.[peer]\n const version = runtimeVersions[peer as 'eslint' | 'typescript']\n\n if (range && version && !semver.satisfies(version, range)) {\n issues.push(`requires ${peer} ${range}, resolved ${version}`)\n }\n }\n }\n\n return {\n aggregatedBasic: name === FULL_PACKAGE_NAME ?\n {\n name: BASIC_PACKAGE_NAME,\n resolved: aggregatedBasic?.manifest.version ?? null,\n resolvedPath: aggregatedBasic?.path ?? null\n } :\n null,\n declared: declared[name],\n issues,\n name,\n resolved: manifest?.version ?? null,\n resolvedPath: resolvedPackage?.path ?? null\n }\n })\n\n return {\n compatible: packages.every(item => item.issues.length === 0),\n packages,\n runtime: runtimeVersions\n }\n}\n\nexport const handleCompatibility = (\n cwd: string = process.cwd(),\n json = false\n): void => {\n const report = createCompatibilityReport(cwd)\n\n if (json) {\n console.log(JSON.stringify(report, null, 2))\n } else {\n console.log([\n `ESLint compatibility: ${report.compatible ? 'compatible' : 'issues found'}`,\n `- Node: ${report.runtime.node}`,\n `- Consumer Node range: ${report.runtime.consumerNodeRange ?? 'not declared'}`,\n `- ESLint: ${report.runtime.eslint ?? 'not installed'}`,\n `- TypeScript: ${report.runtime.typescript ?? 'not installed'}`\n ].join('\\n'))\n\n for (const item of report.packages) {\n console.log(\n `- ${item.name}: ${item.resolved ?? 'not installed'}` +\n (item.resolvedPath ? ` at ${item.resolvedPath}` : '') +\n (item.issues.length > 0 ? ` (${item.issues.join('; ')})` : '')\n )\n\n if (item.aggregatedBasic) {\n console.log(\n ` - Composer ${item.aggregatedBasic.name}: ` +\n (item.aggregatedBasic.resolved ?? 'not resolved') +\n (item.aggregatedBasic.resolvedPath ? ` at ${item.aggregatedBasic.resolvedPath}` : '')\n )\n }\n }\n }\n\n if (!report.compatible) process.exitCode = 1\n}\n"],"mappings":"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","names":[]}
@@ -175,6 +175,13 @@ const FACTORY_ALIAS_REPLACEMENTS = /* @__PURE__ */ new Set([
175
175
  "astroConfig",
176
176
  "gitignore"
177
177
  ]);
178
+ const PARAMETER_PROPERTY_MODIFIERS = /* @__PURE__ */ new Set([
179
+ "override",
180
+ "private",
181
+ "protected",
182
+ "public",
183
+ "readonly"
184
+ ]);
178
185
  const dependencyFields = [
179
186
  "dependencies",
180
187
  "devDependencies",
@@ -323,16 +330,121 @@ const getEnclosingDelimiter = (content, offset) => {
323
330
  }
324
331
  return null;
325
332
  };
333
+ const findClosingDelimiter = (content, start, opener, closer) => {
334
+ let depth = 0;
335
+ for (let index = start; index < content.length; index++) {
336
+ if (content[index] === opener) depth++;
337
+ else if (content[index] === closer && --depth === 0) return index + 1;
338
+ }
339
+ return content.length;
340
+ };
341
+ const getShadowedRanges = (content, name) => {
342
+ const masked = maskNonCode(content);
343
+ const importRanges = getImportRanges(content);
344
+ let searchable = masked;
345
+ for (const range of importRanges.toReversed()) {
346
+ searchable = `${searchable.slice(0, range.start)}${" ".repeat(range.end - range.start)}${searchable.slice(range.end)}`;
347
+ }
348
+ const getArrowRange = (start, arrowIndex) => {
349
+ const relativeBodyStart = searchable.slice(arrowIndex + 2).search(/\S/);
350
+ const bodyStart = relativeBodyStart === -1 ? searchable.length : arrowIndex + 2 + relativeBodyStart;
351
+ const bodyDelimiter = searchable[bodyStart];
352
+ let bodyCloser;
353
+ if (bodyDelimiter === "(") bodyCloser = ")";
354
+ else if (bodyDelimiter === "[") bodyCloser = "]";
355
+ if (bodyDelimiter === "{") {
356
+ return { end: findClosingDelimiter(searchable, bodyStart, "{", "}"), start };
357
+ }
358
+ if (bodyCloser) {
359
+ return { end: findClosingDelimiter(searchable, bodyStart, bodyDelimiter, bodyCloser), start };
360
+ }
361
+ const lineEnd = searchable.indexOf("\n", arrowIndex + 2);
362
+ return { end: lineEnd === -1 ? searchable.length : lineEnd, start };
363
+ };
364
+ const unparenthesizedArrowPattern = new RegExp(`\\b${name}\\b\\s*=>`, "gm");
365
+ const ranges = [...searchable.matchAll(unparenthesizedArrowPattern)].map((match) => getArrowRange(match.index, searchable.indexOf("=>", match.index)));
366
+ const splitAtTopLevel = (value, separator) => {
367
+ const parts = [];
368
+ const closers = { "(": ")", "[": "]", "{": "}" };
369
+ const expectedClosers = [];
370
+ let partStart = 0;
371
+ for (let index = 0; index < value.length; index++) {
372
+ const character = value[index];
373
+ const closer = closers[character];
374
+ if (closer) expectedClosers.push(closer);
375
+ else if (character === expectedClosers.at(-1)) expectedClosers.pop();
376
+ else if (character === separator && expectedClosers.length === 0) {
377
+ parts.push(value.slice(partStart, index));
378
+ partStart = index + 1;
379
+ }
380
+ }
381
+ parts.push(value.slice(partStart));
382
+ return parts;
383
+ };
384
+ const hasBindingName = (value) => {
385
+ let binding = splitAtTopLevel(value, "=")[0].trim();
386
+ if (binding.startsWith("...")) binding = binding.slice(3).trim();
387
+ binding = splitAtTopLevel(binding, ":")[0].trim();
388
+ const bindingParts = binding.split(/\s+/);
389
+ while (PARAMETER_PROPERTY_MODIFIERS.has(bindingParts[0])) bindingParts.shift();
390
+ binding = bindingParts.join(" ");
391
+ if (binding.startsWith("{") && binding.endsWith("}")) {
392
+ return splitAtTopLevel(binding.slice(1, -1), ",").some((property) => {
393
+ const propertyParts = splitAtTopLevel(property, ":");
394
+ return hasBindingName(propertyParts.length > 1 ? propertyParts.slice(1).join(":") : property);
395
+ });
396
+ }
397
+ if (binding.startsWith("[") && binding.endsWith("]")) {
398
+ return splitAtTopLevel(binding.slice(1, -1), ",").some(hasBindingName);
399
+ }
400
+ return binding.replace(/\?$/, "") === name;
401
+ };
402
+ const hasParameterBinding = (parameters) => splitAtTopLevel(parameters, ",").some(hasBindingName);
403
+ const parameterOwnerPattern = /(?:\bfunction\b[^()]*|\bcatch|\b(?:constructor|[a-zA-Z_$][\w$]*))\s*$/;
404
+ for (let parameterStart = 0; parameterStart < searchable.length; parameterStart++) {
405
+ if (searchable[parameterStart] !== "(") continue;
406
+ const parameterEnd = findClosingDelimiter(searchable, parameterStart, "(", ")");
407
+ if (searchable[parameterEnd - 1] !== ")") continue;
408
+ const parameters = searchable.slice(parameterStart + 1, parameterEnd - 1);
409
+ if (!hasParameterBinding(parameters)) continue;
410
+ const relativeBodyStart = searchable.slice(parameterEnd).search(/\S/);
411
+ const bodyStart = relativeBodyStart === -1 ? searchable.length : parameterEnd + relativeBodyStart;
412
+ if (searchable.startsWith("=>", bodyStart)) {
413
+ ranges.push(getArrowRange(parameterStart, bodyStart));
414
+ continue;
415
+ }
416
+ if (searchable[bodyStart] !== "{") continue;
417
+ const ownerMatch = parameterOwnerPattern.exec(searchable.slice(0, parameterStart));
418
+ if (!ownerMatch) continue;
419
+ ranges.push({
420
+ end: findClosingDelimiter(searchable, bodyStart, "{", "}"),
421
+ start: ownerMatch.index
422
+ });
423
+ }
424
+ const blockBindingPattern = new RegExp(`\\b(?:const|let|var|function|class)\\s+${name}\\b`, "g");
425
+ for (const match of searchable.matchAll(blockBindingPattern)) {
426
+ const blockStart = searchable.lastIndexOf("{", match.index);
427
+ if (blockStart !== -1) {
428
+ ranges.push({
429
+ end: findClosingDelimiter(searchable, blockStart, "{", "}"),
430
+ start: blockStart
431
+ });
432
+ }
433
+ }
434
+ return ranges;
435
+ };
326
436
  const replaceBindingReferences = (content, from, to, invokeReferences = false) => {
327
437
  const importRanges = getImportRanges(content);
328
438
  const masked = maskNonCode(content);
329
439
  const identifierPattern = new RegExp(`\\b${from}\\b`, "g");
330
440
  const matches = [...masked.matchAll(identifierPattern)];
441
+ const shadowedRanges = getShadowedRanges(content, from);
331
442
  let updated = content;
332
443
  for (const match of matches.reverse()) {
333
444
  const offset = match.index;
334
445
  const isImportSpecifier = importRanges.some((range) => offset >= range.start && offset < range.end);
335
- if (isImportSpecifier) continue;
446
+ const isShadowed = shadowedRanges.some((range) => offset >= range.start && offset < range.end);
447
+ if (isImportSpecifier || isShadowed) continue;
336
448
  const precedingContent = masked.slice(0, offset).trimEnd();
337
449
  const preceding = precedingContent.at(-1);
338
450
  const following = masked.slice(offset + from.length);
@@ -356,24 +468,27 @@ const replaceImportedAlias = (content, from, to, invokeReferences = false) => {
356
468
  const owner = REMOVED_ALIAS_OWNERS[from];
357
469
  const acceptedPackages = /* @__PURE__ */ new Set([BASIC_PACKAGE, FULL_PACKAGE, ...owner ? [owner] : []]);
358
470
  const importPattern = /import\s*\{([^}]*)\}\s*from\s*(['"])(@santi020k\/eslint-config-[^'"]+)\2/g;
359
- let updated = content.replace(importPattern, (statement, specifierText, _quote, packageName) => {
360
- if (!acceptedPackages.has(packageName)) return statement;
361
- const specifiers = specifierText.split(",");
362
- const rewritten = specifiers.map((specifier) => {
363
- const aliasMatch = /^(\s*)([a-zA-Z_$][\w$]*)\s+as\s+([a-zA-Z_$][\w$]*)(\s*)$/.exec(specifier);
364
- if (aliasMatch?.[2] === from) {
365
- const localName = aliasMatch[3];
366
- bindings.push({ from: localName, to: localName });
367
- return `${aliasMatch[1]}${to} as ${localName}${aliasMatch[4]}`;
368
- }
369
- const directMatch = /^(\s*)([a-zA-Z_$][\w$]*)(\s*)$/.exec(specifier);
370
- if (directMatch?.[2] !== from) return specifier;
371
- bindings.push({ from, to });
372
- return `${directMatch[1]}${to}${directMatch[3]}`;
373
- });
374
- const rewrittenText = rewritten.join(",");
375
- return rewrittenText === specifierText ? statement : statement.replace(specifierText, rewrittenText);
376
- });
471
+ let updated = content.replace(
472
+ importPattern,
473
+ (statement, specifierText, _quote, packageName) => {
474
+ if (!acceptedPackages.has(packageName)) return statement;
475
+ const specifiers = specifierText.split(",");
476
+ const rewritten = specifiers.map((specifier) => {
477
+ const aliasMatch = /^(\s*)([a-zA-Z_$][\w$]*)\s+as\s+([a-zA-Z_$][\w$]*)(\s*)$/.exec(specifier);
478
+ if (aliasMatch?.[2] === from) {
479
+ const localName = aliasMatch[3];
480
+ bindings.push({ from: localName, to: localName });
481
+ return `${aliasMatch[1]}${to} as ${localName}${aliasMatch[4]}`;
482
+ }
483
+ const directMatch = /^(\s*)([a-zA-Z_$][\w$]*)(\s*)$/.exec(specifier);
484
+ if (directMatch?.[2] !== from) return specifier;
485
+ bindings.push({ from, to });
486
+ return `${directMatch[1]}${to}${directMatch[3]}`;
487
+ });
488
+ const rewrittenText = rewritten.join(",");
489
+ return rewrittenText === specifierText ? statement : statement.replace(specifierText, rewrittenText);
490
+ }
491
+ );
377
492
  for (const binding of bindings) {
378
493
  updated = replaceBindingReferences(updated, binding.from, binding.to, invokeReferences);
379
494
  }
@@ -420,17 +535,234 @@ const hasCodePropertySeparator = (codeContent, match) => {
420
535
  const matchIndex = match.index ?? -1;
421
536
  return matchIndex >= 0 && separatorOffset >= 0 && codeContent[matchIndex + separatorOffset] === ":";
422
537
  };
538
+ const replaceCodeProperty = (content, property, replacement, matches) => {
539
+ const masked = maskNonCode(content, true);
540
+ const pattern = new RegExp(`\\b${property}\\b(?=\\s*:)`, "g");
541
+ const propertyMatches = matches ?? [...masked.matchAll(pattern)];
542
+ let updated = content;
543
+ for (const match of propertyMatches.toReversed()) {
544
+ const { index } = match;
545
+ if (index === void 0) continue;
546
+ updated = `${updated.slice(0, index)}${replacement}${updated.slice(index + property.length)}`;
547
+ }
548
+ return updated;
549
+ };
550
+ const toFeatureName = (value) => {
551
+ const trimmed = value.trim();
552
+ const enumMatch = /^(?:Extension|Format|Library|Testing|Tool)\.([a-zA-Z0-9_$]+)$/.exec(trimmed);
553
+ const stringMatch = /^(['"])([^'"]+)\1$/.exec(trimmed);
554
+ const raw = enumMatch?.[1] ?? stringMatch?.[2];
555
+ return raw ? toKebabCase(raw) : null;
556
+ };
557
+ const getOwningObjectStart = (structure, position) => {
558
+ const objectStarts = [];
559
+ for (let index = 0; index < position; index++) {
560
+ if (structure[index] === "{") objectStarts.push(index);
561
+ if (structure[index] === "}") objectStarts.pop();
562
+ }
563
+ return objectStarts.at(-1);
564
+ };
565
+ const getDefineConfigOptionOwners = (searchable) => {
566
+ const owners = new Set(
567
+ [...searchable.matchAll(/\bdefineConfig\s*\(\s*\{/g)].map((match) => match.index + match[0].lastIndexOf("{"))
568
+ );
569
+ const optionNames = new Set(
570
+ [...searchable.matchAll(/\bdefineConfig\s*\(\s*([a-zA-Z_$][\w$]*)\b/g)].map((match) => match[1])
571
+ );
572
+ for (const optionName of optionNames) {
573
+ const bindingPattern = new RegExp(`\\b(?:const|let|var)\\s+${optionName}\\s*=\\s*\\{`, "g");
574
+ for (const match of searchable.matchAll(bindingPattern)) {
575
+ owners.add(match.index + match[0].lastIndexOf("{"));
576
+ }
577
+ }
578
+ return owners;
579
+ };
580
+ const getDirectChildObjectStarts = (structure, containerStart) => {
581
+ const children = [];
582
+ const objectStarts = [containerStart];
583
+ for (let index = containerStart + 1; index < structure.length && objectStarts.length > 0; index++) {
584
+ if (structure[index] === "{") {
585
+ if (objectStarts.at(-1) === containerStart) children.push(index);
586
+ objectStarts.push(index);
587
+ } else if (structure[index] === "}") {
588
+ objectStarts.pop();
589
+ }
590
+ }
591
+ return children;
592
+ };
593
+ const getOwnedObjectPropertyStarts = (searchable, structure, property, owners) => {
594
+ const pattern = new RegExp(`\\b${property}\\b\\s*:\\s*\\{`, "g");
595
+ return [...searchable.matchAll(pattern)].flatMap((match) => {
596
+ const owner = getOwningObjectStart(structure, match.index);
597
+ const objectStart = match.index + match[0].lastIndexOf("{");
598
+ return owner !== void 0 && owners.has(owner) && hasCodePropertySeparator(structure, match) && structure[objectStart] === "{" ? [objectStart] : [];
599
+ });
600
+ };
601
+ const getBasicOptionOwners = (searchable, structure) => {
602
+ const owners = getDefineConfigOptionOwners(searchable);
603
+ for (const projectDefaultsOwner of getOwnedObjectPropertyStarts(
604
+ searchable,
605
+ structure,
606
+ "projectDefaults",
607
+ owners
608
+ )) {
609
+ owners.add(projectDefaultsOwner);
610
+ }
611
+ const visitedProjectContainers = /* @__PURE__ */ new Set();
612
+ let projectContainers = getOwnedObjectPropertyStarts(searchable, structure, "projects", owners);
613
+ while (projectContainers.length > 0) {
614
+ for (const container of projectContainers) {
615
+ visitedProjectContainers.add(container);
616
+ for (const projectOwner of getDirectChildObjectStarts(structure, container)) {
617
+ owners.add(projectOwner);
618
+ }
619
+ }
620
+ projectContainers = getOwnedObjectPropertyStarts(searchable, structure, "projects", owners).filter((container) => !visitedProjectContainers.has(container));
621
+ }
622
+ return owners;
623
+ };
624
+ const getOwnedPropertyMatches = (searchable, structure, property, owners) => {
625
+ const pattern = new RegExp(`\\b${property}\\b\\s*:`, "g");
626
+ return [...searchable.matchAll(pattern)].flatMap((match) => {
627
+ const owner = getOwningObjectStart(structure, match.index);
628
+ return owner !== void 0 && owners.has(owner) && hasCodePropertySeparator(structure, match) ? [match] : [];
629
+ });
630
+ };
631
+ const modernizeLiteralFeatureArrays = (content) => {
632
+ const searchable = maskNonCode(content, true);
633
+ const structure = maskNonCode(content);
634
+ const optionOwners = getBasicOptionOwners(searchable, structure);
635
+ const selections = [...searchable.matchAll(
636
+ /\b(?:extensions|formats|libraries|testing|tools)\b\s*:\s*\[([\s\S]*?)\]\s*,?/g
637
+ )].flatMap((match) => {
638
+ if (/\/[/*]/.test(content.slice(match.index, match.index + match[0].length))) return [];
639
+ const features2 = match[1].split(",").filter(Boolean).map(toFeatureName);
640
+ const owner = getOwningObjectStart(structure, match.index);
641
+ if (owner === void 0 || !optionOwners.has(owner) || !hasCodePropertySeparator(structure, match) || features2.length === 0 || features2.some((feature) => feature === null)) return [];
642
+ return [{
643
+ end: match.index + match[0].length,
644
+ features: features2,
645
+ owner,
646
+ start: match.index
647
+ }];
648
+ });
649
+ if (selections.length === 0) return { changed: false, content, features: [] };
650
+ const features = [...new Set(selections.flatMap((selection) => selection.features))];
651
+ const selectionsByOwner = /* @__PURE__ */ new Map();
652
+ for (const selection of selections) {
653
+ const ownedSelections = selectionsByOwner.get(selection.owner);
654
+ if (ownedSelections) ownedSelections.push(selection);
655
+ else selectionsByOwner.set(selection.owner, [selection]);
656
+ }
657
+ const existingFeaturesByOwner = /* @__PURE__ */ new Map();
658
+ for (const match of searchable.matchAll(/\bfeatures\b\s*:\s*\{/g)) {
659
+ const owner = getOwningObjectStart(structure, match.index);
660
+ if (owner !== void 0 && !existingFeaturesByOwner.has(owner)) {
661
+ existingFeaturesByOwner.set(owner, match);
662
+ }
663
+ }
664
+ const edits = [];
665
+ for (const [owner, ownedSelections] of selectionsByOwner) {
666
+ const ownedFeatures = [...new Set(ownedSelections.flatMap((selection) => selection.features))];
667
+ const first = ownedSelections[0];
668
+ const lineStart = content.lastIndexOf("\n", first.start) + 1;
669
+ const indent = /^\s*/.exec(content.slice(lineStart, first.start))?.[0] ?? "";
670
+ const existingFeatures = existingFeaturesByOwner.get(owner);
671
+ const featureBlock = existingFeatures ? `
672
+ ${ownedFeatures.map((feature) => `${indent} ${JSON.stringify(feature)}: true,`).join("\n")}` : [
673
+ "features: {",
674
+ ...ownedFeatures.map((feature) => `${indent} ${JSON.stringify(feature)}: true,`),
675
+ `${indent}},`
676
+ ].join("\n");
677
+ for (const selection of ownedSelections) {
678
+ edits.push({
679
+ end: selection.end,
680
+ replacement: !existingFeatures && selection === first ? featureBlock : "",
681
+ start: selection.start
682
+ });
683
+ }
684
+ if (existingFeatures) {
685
+ const insertionIndex = (existingFeatures.index ?? 0) + existingFeatures[0].length;
686
+ edits.push({
687
+ end: insertionIndex,
688
+ replacement: featureBlock,
689
+ start: insertionIndex
690
+ });
691
+ }
692
+ }
693
+ let updated = content;
694
+ for (const edit of edits.sort((a, b) => b.start - a.start)) {
695
+ updated = `${updated.slice(0, edit.start)}${edit.replacement}${updated.slice(edit.end)}`;
696
+ }
697
+ return { changed: updated !== content, content: updated, features };
698
+ };
699
+ const modernizeConfigOptions = (content) => {
700
+ const changes = [];
701
+ let updated = content;
702
+ const searchable = maskNonCode(updated, true);
703
+ const structure = maskNonCode(updated);
704
+ const basicOptionOwners = getBasicOptionOwners(searchable, structure);
705
+ const featureOwners = new Set(
706
+ getOwnedPropertyMatches(searchable, structure, "features", basicOptionOwners).flatMap((match) => {
707
+ if (match.index === void 0) return [];
708
+ const owner = getOwningObjectStart(structure, match.index);
709
+ return owner === void 0 ? [] : [owner];
710
+ })
711
+ );
712
+ const integrationMatches = getOwnedPropertyMatches(searchable, structure, "integrations", basicOptionOwners).filter((match) => {
713
+ if (match.index === void 0) return false;
714
+ const owner = getOwningObjectStart(structure, match.index);
715
+ return owner !== void 0 && !featureOwners.has(owner);
716
+ });
717
+ if (integrationMatches.length > 0) {
718
+ updated = replaceCodeProperty(updated, "integrations", "features", integrationMatches);
719
+ changes.push("Replaced the deprecated integrations map with features.");
720
+ }
721
+ const updatedSearchable = maskNonCode(updated, true);
722
+ const updatedStructure = maskNonCode(updated);
723
+ const optionOwners = getDefineConfigOptionOwners(updatedSearchable);
724
+ const rootMatches = getOwnedPropertyMatches(updatedSearchable, updatedStructure, "root", optionOwners);
725
+ const rootAliasMatches = /* @__PURE__ */ new Map([
726
+ ["detectRootDir", getOwnedPropertyMatches(updatedSearchable, updatedStructure, "detectRootDir", optionOwners)],
727
+ ["tsconfigRootDir", getOwnedPropertyMatches(updatedSearchable, updatedStructure, "tsconfigRootDir", optionOwners)]
728
+ ]);
729
+ const rootAliases = [...rootAliasMatches].filter(([, matches]) => matches.length > 0).map(([alias]) => alias);
730
+ if (rootMatches.length === 0 && rootAliases.length === 1) {
731
+ const alias = rootAliases[0];
732
+ updated = replaceCodeProperty(updated, alias, "root", rootAliasMatches.get(alias));
733
+ changes.push(`Replaced ${alias} with the v3 root option.`);
734
+ } else if (rootAliases.length > 0) {
735
+ changes.push(
736
+ `Manual action required: consolidate ${rootAliases.join(" and ")} into root after confirming they resolve to the same directory.`
737
+ );
738
+ }
739
+ const featureMigration = modernizeLiteralFeatureArrays(updated);
740
+ if (featureMigration.changed) {
741
+ updated = featureMigration.content;
742
+ changes.push(`Replaced literal category arrays with the v3 features map (${featureMigration.features.join(", ")}).`);
743
+ }
744
+ if (maskNonCode(updated, true).includes("better-tailwindcss/no-unknown-classes")) {
745
+ changes.push(
746
+ 'Manual action required: replace the raw better-tailwindcss/no-unknown-classes override with projects["<scope>"].tailwind.noUnknownClasses.'
747
+ );
748
+ }
749
+ return { changes, content: updated };
750
+ };
423
751
  const getExplicitConfigFeaturePackages = (content) => {
424
752
  const packages = /* @__PURE__ */ new Set();
425
753
  const codeContent = maskNonCode(content);
426
754
  const searchableContent = maskNonCode(content, true);
755
+ const optionOwners = getBasicOptionOwners(searchableContent, codeContent);
756
+ const isBasicOption = (match) => {
757
+ const owner = getOwningObjectStart(codeContent, match.index ?? -1);
758
+ return owner !== void 0 && optionOwners.has(owner) && hasCodePropertySeparator(codeContent, match);
759
+ };
427
760
  for (const category of Object.keys(FEATURE_PACKAGES)) {
428
761
  const categorySelections = searchableContent.matchAll(
429
- // eslint-disable-next-line security/detect-non-literal-regexp -- category names come from the internal package registry
430
762
  new RegExp(`(?:\\b${category}\\b|['"]${category}['"])\\s*:\\s*\\[([\\s\\S]*?)\\]`, "g")
431
763
  );
432
764
  for (const selection of categorySelections) {
433
- if (hasCodePropertySeparator(codeContent, selection) && selection[1].trim()) {
765
+ if (isBasicOption(selection) && selection[1].trim()) {
434
766
  packages.add(FEATURE_PACKAGES[category]);
435
767
  break;
436
768
  }
@@ -438,7 +770,7 @@ const getExplicitConfigFeaturePackages = (content) => {
438
770
  }
439
771
  const featureSelections = /(?:\b(?:features|integrations)\b|['"](?:features|integrations)['"])\s*:\s*\{([\s\S]*?)\}/g;
440
772
  for (const selection of searchableContent.matchAll(featureSelections)) {
441
- if (!hasCodePropertySeparator(codeContent, selection)) continue;
773
+ if (!isBasicOption(selection)) continue;
442
774
  const enabledFeaturePattern = /(?:['"]([^'"]+)['"]|([a-zA-Z_$][\w$-]*))\s*:\s*true\b/g;
443
775
  for (const match of selection[1].matchAll(enabledFeaturePattern)) {
444
776
  const featureName = (match[1] || match[2]).toLowerCase();
@@ -448,7 +780,7 @@ const getExplicitConfigFeaturePackages = (content) => {
448
780
  }
449
781
  const enumPresetPattern = /(?:\bpreset\b|['"]preset['"])\s*:\s*Preset\.(All|App|CI|Library|Monorepo)/g;
450
782
  for (const match of searchableContent.matchAll(enumPresetPattern)) {
451
- if (!hasCodePropertySeparator(codeContent, match)) continue;
783
+ if (!isBasicOption(match)) continue;
452
784
  const presetName = match[1].toLowerCase();
453
785
  for (const packageName of PRESET_FEATURE_PACKAGES[presetName] ?? []) {
454
786
  packages.add(packageName);
@@ -456,31 +788,39 @@ const getExplicitConfigFeaturePackages = (content) => {
456
788
  }
457
789
  const stringPresetPattern = /(?:\bpreset\b|['"]preset['"])\s*:\s*(['"`])(all|app|ci|library|monorepo)\1/g;
458
790
  for (const match of searchableContent.matchAll(stringPresetPattern)) {
459
- if (!hasCodePropertySeparator(codeContent, match)) continue;
791
+ if (!isBasicOption(match)) continue;
460
792
  for (const packageName of PRESET_FEATURE_PACKAGES[match[2]] ?? []) {
461
793
  packages.add(packageName);
462
794
  }
463
795
  }
464
796
  const strictPattern = /(?:\bstrict\b|['"]strict['"])\s*:\s*(['"`])pedantic\1/g;
465
797
  for (const match of searchableContent.matchAll(strictPattern)) {
466
- if (hasCodePropertySeparator(codeContent, match)) {
798
+ if (isBasicOption(match)) {
467
799
  packages.add(FEATURE_PACKAGES.extensions);
468
800
  break;
469
801
  }
470
802
  }
471
803
  return [...packages];
472
804
  };
805
+ const replacePackageSpecifiers = (content, from, to) => content.replace(
806
+ /(\b(?:from|import|export)\s*(?:\(\s*)?|\brequire\s*\(\s*)(['"])([^'"]+)\2/g,
807
+ (statement, prefix, quote, specifier) => specifier === from ? `${prefix}${quote}${to}${quote}` : statement
808
+ );
473
809
  const migrateConfigToV3 = (content, mode) => {
474
810
  const changes = [];
475
811
  const featurePackages = new Set(getExplicitConfigFeaturePackages(content));
476
812
  let migrated = content;
477
- if (migrated.includes(LITE_PACKAGE)) {
478
- migrated = migrated.replaceAll(LITE_PACKAGE, BASIC_PACKAGE);
813
+ const liteMigrated = replacePackageSpecifiers(migrated, LITE_PACKAGE, BASIC_PACKAGE);
814
+ if (liteMigrated !== migrated) {
815
+ migrated = liteMigrated;
479
816
  changes.push("Replaced the Lite compatibility import with Basic.");
480
817
  }
481
- if (mode === "full" && migrated.includes(BASIC_PACKAGE)) {
482
- migrated = migrated.replaceAll(BASIC_PACKAGE, FULL_PACKAGE);
483
- changes.push("Switched the root config import to the full bundle.");
818
+ if (mode === "full") {
819
+ const fullMigrated = replacePackageSpecifiers(migrated, BASIC_PACKAGE, FULL_PACKAGE);
820
+ if (fullMigrated !== migrated) {
821
+ migrated = fullMigrated;
822
+ changes.push("Switched the root config import to the full bundle.");
823
+ }
484
824
  }
485
825
  if (mode === "lean") {
486
826
  const split = splitBasicIntegrationImports(migrated);
@@ -506,13 +846,21 @@ const migrateConfigToV3 = (content, mode) => {
506
846
  migrated = remixMigrated;
507
847
  changes.push('Moved frameworks.remix to frameworks["react-router"].');
508
848
  }
509
- if (migrated.includes(REMIX_PACKAGE)) {
510
- migrated = migrated.replaceAll(REMIX_PACKAGE, FRAMEWORK_PACKAGES["react-router"]);
849
+ const reactRouterMigrated = replacePackageSpecifiers(
850
+ migrated,
851
+ REMIX_PACKAGE,
852
+ FRAMEWORK_PACKAGES["react-router"]
853
+ );
854
+ if (reactRouterMigrated !== migrated) {
855
+ migrated = reactRouterMigrated;
511
856
  changes.push("Replaced the Remix package import with React Router.");
512
857
  }
513
858
  if (/\bloadModule\b/.test(migrated)) {
514
859
  changes.push("Manual action required: replace loadModule usage with createModuleLoader(resolver).");
515
860
  }
861
+ const modernized = modernizeConfigOptions(migrated);
862
+ migrated = modernized.content;
863
+ changes.push(...modernized.changes);
516
864
  return {
517
865
  changes,
518
866
  content: migrated,
@@ -525,13 +873,18 @@ const shouldUseFullBundle = (configContent, packageJson, forceFull) => {
525
873
  if (configContent) {
526
874
  const codeContent = maskNonCode(configContent);
527
875
  const searchableContent = maskNonCode(configContent, true);
876
+ const optionOwners = getDefineConfigOptionOwners(searchableContent);
877
+ const isRootOption = (match) => {
878
+ const owner = getOwningObjectStart(codeContent, match.index ?? -1);
879
+ return owner !== void 0 && optionOwners.has(owner) && hasCodePropertySeparator(codeContent, match);
880
+ };
528
881
  const enumPresetPattern = /(?:\bpreset\b|['"]preset['"])\s*:\s*Preset\.All/g;
529
882
  for (const match of searchableContent.matchAll(enumPresetPattern)) {
530
- if (hasCodePropertySeparator(codeContent, match)) return true;
883
+ if (isRootOption(match)) return true;
531
884
  }
532
885
  const stringPresetPattern = /(?:\bpreset\b|['"]preset['"])\s*:\s*(['"`])all\1/g;
533
886
  for (const match of searchableContent.matchAll(stringPresetPattern)) {
534
- if (hasCodePropertySeparator(codeContent, match)) return true;
887
+ if (isRootOption(match)) return true;
535
888
  }
536
889
  }
537
890
  return getDeclaredConfigPackages(packageJson).has(FULL_PACKAGE);
@@ -780,3 +1133,4 @@ export {
780
1133
  handleMigrateV3,
781
1134
  migrateConfigToV3
782
1135
  };
1136
+ //# sourceMappingURL=cli-migration.js.map