mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
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
137 lines
4.0 KiB
JavaScript
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,
|
|
};
|
|
},
|
|
};
|