Files
qwen-code/eslint-rules/no-core-root-barrel-import.js
易良 351b5fc51a ci(lint): stop new cli imports from the core package root (#11798)
Importing a value from the @qwen-code/qwen-code-core root evaluates all
of core in every test whose import graph reaches the importing file,
which is where most cli test time goes (#10908). The existing rule stops
at packages/core/src, so cli kept adding root imports: 446 production
files at #10957's merge, 465 eight days later.

Enable @typescript-eslint/no-restricted-imports for cli production files
with type-only imports allowed, reusing the barrel specifier set from
no-core-root-barrel-import. The 372 files that value-import the root
today are listed in eslint.legacy-core-barrel-imports.mjs; the list only
shrinks. The new file is registered wherever eslint.legacy-filenames.mjs
is: the lint-gate freshness list, the autofix sensitive-file classifier,
and the hygiene fix agent's deny list.

ESLint and Prettier (--experimental-cli) were run on the changed files,
and the new block was exercised through ESLint's API against the real
eslint.config.js. The scripts/tests vitest suites were not run locally.

Refs #10908
2026-09-14 04:49:03 +00:00

137 lines
4.0 KiB
JavaScript

/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Prevents core production modules from importing the core root barrel.
*/
import path from 'node:path';
const TEST_OR_FIXTURE_SEGMENTS = new Set(['__tests__', 'fixtures']);
const CORE_SRC_MARKER = 'packages/core/src/';
function isCoreProductionFile(filename) {
if (!filename || filename === '<input>' || filename === '<text>')
return false;
const normalized = path.normalize(filename).replaceAll('\\', '/');
const start = normalized.lastIndexOf(CORE_SRC_MARKER);
if (start < 0) return false;
const relativePath = normalized.slice(start + CORE_SRC_MARKER.length);
const segments = relativePath.split('/');
return (
!segments.some((segment) => TEST_OR_FIXTURE_SEGMENTS.has(segment)) &&
!/\.(test|spec)\.[cm]?[jt]sx?$/.test(relativePath)
);
}
const CORE_PACKAGE_SPECIFIER = '@qwen-code/qwen-code-core';
// The package exports map ("./*", "./src/*", "./dist/*") also exposes the root
// barrel through these self-reference subpaths.
export const CORE_BARREL_SPECIFIERS = new Set([
CORE_PACKAGE_SPECIFIER,
`${CORE_PACKAGE_SPECIFIER}/index.js`,
`${CORE_PACKAGE_SPECIFIER}/src/index.js`,
`${CORE_PACKAGE_SPECIFIER}/src/index.ts`,
`${CORE_PACKAGE_SPECIFIER}/dist/index.js`,
]);
function resolvesToCoreRootBarrel(filename, importedPath) {
if (CORE_BARREL_SPECIFIERS.has(importedPath)) return true;
if (!importedPath.startsWith('.')) return false;
const normalized = path.normalize(filename).replaceAll('\\', '/');
const sourceRoot = path.resolve(
normalized.slice(
0,
normalized.lastIndexOf(CORE_SRC_MARKER) + CORE_SRC_MARKER.length,
),
);
const resolvedImport = path.resolve(path.dirname(filename), importedPath);
const relativeToSource = path
.relative(sourceRoot, resolvedImport)
.replaceAll('\\', '/');
return (
relativeToSource === 'index.js' ||
relativeToSource === 'index.ts' ||
relativeToSource === '../index.js' ||
relativeToSource === '../index.ts' ||
relativeToSource === '../dist/index.js'
);
}
export default {
meta: {
type: 'problem',
docs: {
description:
'Disallow core production modules from importing the core root barrel.',
},
schema: [],
messages: {
noCoreRootBarrelImport:
'Core production modules must import symbols from their direct owner modules, not the core root barrel.',
},
},
create(context) {
const filename = context.filename;
if (!isCoreProductionFile(filename)) {
return {};
}
function reportIfBarrel(sourceNode, importedPath) {
if (
typeof importedPath === 'string' &&
resolvesToCoreRootBarrel(filename, importedPath)
) {
context.report({
node: sourceNode,
messageId: 'noCoreRootBarrelImport',
});
}
}
function checkSource(node) {
if (node.source && typeof node.source.value === 'string') {
reportIfBarrel(node.source, node.source.value);
}
}
function checkDynamicImport(node) {
const source = node.source;
if (source.type === 'Literal') {
reportIfBarrel(source, source.value);
} else if (
source.type === 'TemplateLiteral' &&
source.quasis.length === 1
) {
reportIfBarrel(source, source.quasis[0].value.cooked);
}
}
// Inline type imports (`type X = import('../index.js').X;`) parse as
// TSImportType under typescript-eslint, not as ImportDeclaration.
function checkTSImportType(node) {
const argument = node.argument;
if (
argument &&
argument.type === 'TSLiteralType' &&
argument.literal &&
typeof argument.literal.value === 'string'
) {
reportIfBarrel(argument.literal, argument.literal.value);
}
}
return {
ImportDeclaration: checkSource,
ExportNamedDeclaration: checkSource,
ExportAllDeclaration: checkSource,
ImportExpression: checkDynamicImport,
TSImportType: checkTSImportType,
};
},
};