Skip to content

Commit d26fc42

Browse files
committed
fix(core): close self-reference boundary gaps
1 parent 9aecfff commit d26fc42

2 files changed

Lines changed: 74 additions & 12 deletions

File tree

eslint-rules/no-core-utils-upward-import.js

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,25 @@
1717
* behind `debugLogger`).
1818
*/
1919

20+
import { readFileSync } from 'node:fs';
2021
import path from 'node:path';
22+
import { URL } from 'node:url';
2123

2224
const CORE_SRC_MARKER = 'packages/core/src/';
2325
const UTILS_SRC_MARKER = 'packages/core/src/utils/';
2426
const CORE_PACKAGE_SPECIFIER = '@qwen-code/qwen-code-core';
2527
const CORE_PACKAGE_SRC_PREFIX = `${CORE_PACKAGE_SPECIFIER}/src/`;
2628
const CORE_PACKAGE_DIST_PREFIX = `${CORE_PACKAGE_SPECIFIER}/dist/`;
29+
const CORE_PACKAGE_SUBPATH_PREFIX = `${CORE_PACKAGE_SPECIFIER}/`;
30+
31+
// Resolve named self-reference subpaths from the package contract so this
32+
// boundary cannot drift from packages/core/package.json.
33+
const CORE_PACKAGE_EXPORTS = JSON.parse(
34+
readFileSync(
35+
new URL('../packages/core/package.json', import.meta.url),
36+
'utf8',
37+
),
38+
).exports;
2739

2840
// Deferred inversions, keyed by the utils-relative importer. Targets are
2941
// relative to packages/core/src and omit their extension. See the file header
@@ -54,6 +66,38 @@ function stripExtension(rel) {
5466
return rel.replace(/\.(js|ts|tsx|mjs|cjs)$/, '');
5567
}
5668

69+
function corePackageSourcePath(importedPath) {
70+
if (importedPath.startsWith(CORE_PACKAGE_SRC_PREFIX)) {
71+
return importedPath.slice(CORE_PACKAGE_SRC_PREFIX.length);
72+
}
73+
74+
if (importedPath.startsWith(CORE_PACKAGE_DIST_PREFIX)) {
75+
const distRelative = importedPath.slice(CORE_PACKAGE_DIST_PREFIX.length);
76+
return distRelative.startsWith('src/')
77+
? distRelative.slice('src/'.length)
78+
: distRelative;
79+
}
80+
81+
if (!importedPath.startsWith(CORE_PACKAGE_SUBPATH_PREFIX)) {
82+
return null;
83+
}
84+
85+
const exportKey = `./${importedPath.slice(CORE_PACKAGE_SUBPATH_PREFIX.length)}`;
86+
const exportEntry = CORE_PACKAGE_EXPORTS[exportKey];
87+
const exportTarget =
88+
typeof exportEntry === 'string' ? exportEntry : exportEntry?.import;
89+
if (typeof exportTarget !== 'string') {
90+
return null;
91+
}
92+
if (exportTarget.startsWith('./dist/src/')) {
93+
return exportTarget.slice('./dist/src/'.length);
94+
}
95+
if (exportTarget.startsWith('./src/')) {
96+
return exportTarget.slice('./src/'.length);
97+
}
98+
return null;
99+
}
100+
57101
export default {
58102
meta: {
59103
type: 'problem',
@@ -89,18 +133,12 @@ export default {
89133
let resolved;
90134
if (importedPath.startsWith('.')) {
91135
resolved = path.resolve(path.dirname(filename), importedPath);
92-
} else if (importedPath.startsWith(CORE_PACKAGE_SRC_PREFIX)) {
93-
resolved = path.resolve(
94-
srcRoot,
95-
importedPath.slice(CORE_PACKAGE_SRC_PREFIX.length),
96-
);
97-
} else if (importedPath.startsWith(CORE_PACKAGE_DIST_PREFIX)) {
98-
resolved = path.resolve(
99-
srcRoot,
100-
importedPath.slice(CORE_PACKAGE_DIST_PREFIX.length),
101-
);
102136
} else {
103-
return;
137+
const sourcePath = corePackageSourcePath(importedPath);
138+
if (!sourcePath) {
139+
return;
140+
}
141+
resolved = path.resolve(srcRoot, sourcePath);
104142
}
105143

106144
// Leave cross-package relative imports to no-relative-cross-package-imports.

scripts/tests/no-core-utils-upward-import.test.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,13 @@ describe('no-core-utils-upward-import', () => {
7878
).toHaveLength(1);
7979
expect(
8080
runRule(
81-
"import { Storage } from '@qwen-code/qwen-code-core/dist/config/storage.js';",
81+
"import { Storage } from '@qwen-code/qwen-code-core/dist/src/config/storage.js';",
82+
'packages/core/src/utils/foo.ts',
83+
),
84+
).toHaveLength(1);
85+
expect(
86+
runRule(
87+
"import { wireGoal } from '@qwen-code/qwen-code-core/goalWire';",
8288
'packages/core/src/utils/foo.ts',
8389
),
8490
).toHaveLength(1);
@@ -127,6 +133,18 @@ describe('no-core-utils-upward-import', () => {
127133
'packages/core/src/utils/foo.ts',
128134
),
129135
).toHaveLength(0);
136+
expect(
137+
runRule(
138+
"import { TranscriptRecordType } from '@qwen-code/qwen-code-core/transcriptRecords';",
139+
'packages/core/src/utils/foo.ts',
140+
),
141+
).toHaveLength(0);
142+
expect(
143+
runRule(
144+
"import { ToolError } from '@qwen-code/qwen-code-core/dist/src/utils/errors.js';",
145+
'packages/core/src/utils/foo.ts',
146+
),
147+
).toHaveLength(0);
130148
});
131149

132150
it('allowlists the deferred debugLogger inversions', () => {
@@ -148,6 +166,12 @@ describe('no-core-utils-upward-import', () => {
148166
'packages/core/src/utils/debugLogger.ts',
149167
),
150168
).toHaveLength(0);
169+
expect(
170+
runRule(
171+
"import { Storage } from '@qwen-code/qwen-code-core/dist/src/config/storage.js';",
172+
'packages/core/src/utils/debugLogger.ts',
173+
),
174+
).toHaveLength(0);
151175
});
152176

153177
it('limits deferred inversions to debugLogger', () => {

0 commit comments

Comments
 (0)