Skip to content

Commit 5e9e6ad

Browse files
authored
Fix accidental relative imports from non namespace barrels (#59544)
1 parent 1f54d0a commit 5e9e6ad

File tree

6 files changed

+113
-26
lines changed

6 files changed

+113
-26
lines changed

eslint.config.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,20 @@ export default tseslint.config(
210210
{ name: "clearImmediate" },
211211
{ name: "performance" },
212212
],
213+
"local/no-direct-import": "error",
213214
},
214215
},
215216
{
216217
files: ["src/harness/**", "src/testRunner/**"],
217218
rules: {
218219
"no-restricted-globals": "off",
220+
"local/no-direct-import": "off",
221+
},
222+
},
223+
{
224+
files: ["src/**/_namespaces/**"],
225+
rules: {
226+
"local/no-direct-import": "off",
219227
},
220228
},
221229
{
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const { createRule } = require("./utils.cjs");
2+
const path = require("path");
3+
4+
/** @import { TSESTree } from "@typescript-eslint/utils" */
5+
void 0;
6+
7+
module.exports = createRule({
8+
name: "no-direct-import",
9+
meta: {
10+
docs: {
11+
description: ``,
12+
},
13+
messages: {
14+
noDirectImport: `This import relatively references another project; did you mean to import from a local _namespace module?`,
15+
},
16+
schema: [],
17+
type: "problem",
18+
},
19+
defaultOptions: [],
20+
21+
create(context) {
22+
const containingFileName = context.filename;
23+
const containingDirectory = path.dirname(containingFileName);
24+
25+
/** @type {(node: TSESTree.ImportDeclaration | TSESTree.TSImportEqualsDeclaration) => void} */
26+
const check = node => {
27+
let source;
28+
if (node.type === "TSImportEqualsDeclaration") {
29+
const moduleReference = node.moduleReference;
30+
if (
31+
moduleReference.type === "TSExternalModuleReference"
32+
&& moduleReference.expression.type === "Literal"
33+
&& typeof moduleReference.expression.value === "string"
34+
) {
35+
source = moduleReference.expression;
36+
}
37+
}
38+
else {
39+
source = node.source;
40+
}
41+
42+
if (!source) return;
43+
44+
const p = source.value;
45+
46+
// These appear in place of public API imports.
47+
if (p.endsWith("../typescript/typescript.js")) return;
48+
49+
// The below is similar to https://github.com/microsoft/DefinitelyTyped-tools/blob/main/packages/eslint-plugin/src/rules/no-bad-reference.ts
50+
51+
// Any relative path that goes to the wrong place will contain "..".
52+
if (!p.includes("..")) return;
53+
54+
const parts = p.split("/");
55+
let cwd = containingDirectory;
56+
for (const part of parts) {
57+
if (part === "" || part === ".") {
58+
continue;
59+
}
60+
if (part === "..") {
61+
cwd = path.dirname(cwd);
62+
}
63+
else {
64+
cwd = path.join(cwd, part);
65+
}
66+
67+
if (path.basename(cwd) === "src") {
68+
context.report({
69+
messageId: "noDirectImport",
70+
node: source,
71+
});
72+
}
73+
}
74+
};
75+
76+
return {
77+
ImportDeclaration: check,
78+
TSImportEqualsDeclaration: check,
79+
};
80+
},
81+
});

src/services/codefixes/fixMissingTypeAnnotationOnExports.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
import {
2+
createCodeFixAction,
3+
createCombinedCodeActions,
4+
createImportAdder,
5+
eachDiagnostic,
6+
registerCodeFix,
7+
typePredicateToAutoImportableTypeNode,
8+
typeToAutoImportableTypeNode,
9+
} from "../_namespaces/ts.codefix.js";
110
import {
211
ArrayBindingPattern,
312
ArrayLiteralExpression,
@@ -97,17 +106,7 @@ import {
97106
VariableStatement,
98107
walkUpParenthesizedExpressions,
99108
} from "../_namespaces/ts.js";
100-
101-
import {
102-
createCodeFixAction,
103-
createCombinedCodeActions,
104-
createImportAdder,
105-
eachDiagnostic,
106-
registerCodeFix,
107-
typePredicateToAutoImportableTypeNode,
108-
typeToAutoImportableTypeNode,
109-
} from "../_namespaces/ts.codefix.js";
110-
import { getIdentifierForNode } from "../refactors/helpers.js";
109+
import { getIdentifierForNode } from "../_namespaces/ts.refactor.js";
111110

112111
const fixId = "fixMissingTypeAnnotationOnExports";
113112

src/services/pasteEdits.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
import { findIndex } from "../compiler/core.js";
21
import {
32
CancellationToken,
4-
Program,
5-
SourceFile,
6-
Statement,
7-
SymbolFlags,
8-
TextRange,
9-
UserPreferences,
10-
} from "../compiler/types.js";
11-
import {
123
codefix,
134
Debug,
145
fileShouldUseJavaScriptRequire,
6+
findIndex,
157
forEachChild,
168
formatting,
179
getQuotePreference,
1810
isIdentifier,
11+
Program,
12+
SourceFile,
13+
Statement,
14+
SymbolFlags,
1915
textChanges,
16+
TextRange,
17+
UserPreferences,
2018
} from "./_namespaces/ts.js";
2119
import { addTargetFileImports } from "./refactors/helpers.js";
2220
import {

src/services/refactors/moveToFile.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getModuleSpecifier } from "../../compiler/_namespaces/ts.moduleSpecifiers.js";
21
import {
32
ApplicableRefactorInfo,
43
arrayFrom,
@@ -118,6 +117,7 @@ import {
118117
ModifierLike,
119118
ModuleDeclaration,
120119
ModuleKind,
120+
moduleSpecifiers,
121121
moduleSpecifierToValidIdentifier,
122122
NamedImportBindings,
123123
Node,
@@ -157,8 +157,10 @@ import {
157157
VariableDeclarationList,
158158
VariableStatement,
159159
} from "../_namespaces/ts.js";
160-
import { addTargetFileImports } from "../_namespaces/ts.refactor.js";
161-
import { registerRefactor } from "../refactorProvider.js";
160+
import {
161+
addTargetFileImports,
162+
registerRefactor,
163+
} from "../_namespaces/ts.refactor.js";
162164

163165
const refactorNameForMoveToFile = "Move to file";
164166
const description = getLocaleSpecificMessage(Diagnostics.Move_to_file);
@@ -358,7 +360,7 @@ function updateImportsInOtherFiles(
358360

359361
if (getStringComparer(!program.useCaseSensitiveFileNames())(pathToTargetFileWithExtension, sourceFile.fileName) === Comparison.EqualTo) return;
360362

361-
const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.fileName, pathToTargetFileWithExtension, createModuleSpecifierResolutionHost(program, host));
363+
const newModuleSpecifier = moduleSpecifiers.getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.fileName, pathToTargetFileWithExtension, createModuleSpecifierResolutionHost(program, host));
362364
const newImportDeclaration = filterImport(importNode, makeStringLiteral(newModuleSpecifier, quotePreference), shouldMove);
363365
if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration);
364366

src/services/stringCompletions.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getModuleSpecifierPreferences } from "../compiler/moduleSpecifiers.js";
21
import {
32
CompletionKind,
43
createCompletionDetails,
@@ -823,7 +822,7 @@ function getFilenameWithExtensionOption(name: string, program: Program, extensio
823822
return { name, extension: tryGetExtensionFromPath(name) };
824823
}
825824

826-
let allowedEndings = getModuleSpecifierPreferences(
825+
let allowedEndings = moduleSpecifiers.getModuleSpecifierPreferences(
827826
{ importModuleSpecifierEnding: extensionOptions.endingPreference },
828827
program,
829828
program.getCompilerOptions(),

0 commit comments

Comments
 (0)