Skip to content

Commit f65ca8b

Browse files
fix: no-missing-import for typescript (#24)
* refactor typescript logic into utilities, fix no-missing-import for ts files. Closes #23 * Apply suggestions from code review * fix valid test Co-authored-by: 唯然 <[email protected]>
1 parent 5928695 commit f65ca8b

File tree

6 files changed

+102
-37
lines changed

6 files changed

+102
-37
lines changed

lib/rules/file-extension-in-import.js

+6-34
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,11 @@
66

77
const path = require("path")
88
const fs = require("fs")
9+
const mapTypescriptExtension = require("../util/map-typescript-extension")
910
const visitImport = require("../util/visit-import")
1011
const packageNamePattern = /^(?:@[^/\\]+[/\\])?[^/\\]+$/u
1112
const corePackageOverridePattern =
1213
/^(?:assert|async_hooks|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|http2|https|inspector|module|net|os|path|perf_hooks|process|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|trace_events|tty|url|util|v8|vm|worker_threads|zlib)[/\\]$/u
13-
const typescriptFileExtensionsMapping = {
14-
".ts": ".js",
15-
".cts": ".cjs",
16-
".mts": ".mjs",
17-
}
1814

1915
/**
2016
* Get all file extensions of the files which have the same basename.
@@ -36,27 +32,6 @@ function getExistingExtensions(filePath) {
3632
}
3733
}
3834

39-
/**
40-
* Get the file extension that should be added in an import statement,
41-
* based on the given file extension of the referenced file.
42-
*
43-
* For example, in typescript, when referencing another typescript from a typescript file,
44-
* a .js extension should be used instead of the original .ts extension of the referenced file.
45-
* @param {string} referencedFileExt The original file extension of the referenced file.
46-
* @param {string} referencingFileExt The original file extension of the file the contains the import statement.
47-
* @returns {string} The file extension to append to the import statement.
48-
*/
49-
function getFileExtensionToAdd(referencedFileExt, referencingFileExt) {
50-
if (
51-
referencingFileExt in typescriptFileExtensionsMapping &&
52-
referencedFileExt in typescriptFileExtensionsMapping
53-
) {
54-
return typescriptFileExtensionsMapping[referencedFileExt]
55-
}
56-
57-
return referencedFileExt
58-
}
59-
6035
module.exports = {
6136
meta: {
6237
docs: {
@@ -104,19 +79,16 @@ module.exports = {
10479

10580
// Get extension.
10681
const originalExt = path.extname(name)
107-
const resolvedExt = path.extname(filePath)
10882
const existingExts = getExistingExtensions(filePath)
109-
const ext = resolvedExt || existingExts.join(" or ")
83+
const ext = path.extname(filePath) || existingExts.join(" or ")
11084
const style = overrideStyle[ext] || defaultStyle
11185

11286
// Verify.
11387
if (style === "always" && ext !== originalExt) {
114-
const referencingFileExt = path.extname(
115-
context.getPhysicalFilename()
116-
)
117-
const fileExtensionToAdd = getFileExtensionToAdd(
118-
ext,
119-
referencingFileExt
88+
const fileExtensionToAdd = mapTypescriptExtension(
89+
context,
90+
filePath,
91+
ext
12092
)
12193
context.report({
12294
node,

lib/util/check-existence.js

+18-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
*/
55
"use strict"
66

7+
const path = require("path")
78
const exists = require("./exists")
89
const getAllowModules = require("./get-allow-modules")
10+
const isTypescript = require("./is-typescript")
11+
const mapTypescriptExtension = require("../util/map-typescript-extension")
912

1013
/**
1114
* Checks whether or not each requirement target exists.
@@ -17,16 +20,28 @@ const getAllowModules = require("./get-allow-modules")
1720
* @param {ImportTarget[]} targets - A list of target information to check.
1821
* @returns {void}
1922
*/
20-
module.exports = function checkForExistence(context, targets) {
23+
module.exports = function checkExistence(context, targets) {
2124
const allowed = new Set(getAllowModules(context))
2225

2326
for (const target of targets) {
2427
const missingModule =
2528
target.moduleName != null &&
2629
!allowed.has(target.moduleName) &&
2730
target.filePath == null
28-
const missingFile =
29-
target.moduleName == null && !exists(target.filePath)
31+
32+
let missingFile = target.moduleName == null && !exists(target.filePath)
33+
if (missingFile && isTypescript(context)) {
34+
const parsed = path.parse(target.filePath)
35+
const reversedExt = mapTypescriptExtension(
36+
context,
37+
target.filePath,
38+
parsed.ext,
39+
true
40+
)
41+
const reversedPath =
42+
path.resolve(parsed.dir, parsed.name) + reversedExt
43+
missingFile = target.moduleName == null && !exists(reversedPath)
44+
}
3045

3146
if (missingModule || missingFile) {
3247
context.report({

lib/util/is-typescript.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"use strict"
2+
3+
const path = require("path")
4+
5+
const typescriptExtensions = [".ts", ".cts", ".mts"]
6+
7+
/**
8+
* Determine if the context source file is typescript.
9+
*
10+
* @param {RuleContext} context - A context
11+
* @returns {boolean}
12+
*/
13+
module.exports = function isTypescript(context) {
14+
const sourceFileExt = path.extname(context.getPhysicalFilename())
15+
return typescriptExtensions.includes(sourceFileExt)
16+
}

lib/util/map-typescript-extension.js

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"use strict"
2+
3+
const path = require("path")
4+
const isTypescript = require("../util/is-typescript")
5+
6+
const mapping = {
7+
".ts": ".js",
8+
".cts": ".cjs",
9+
".mts": ".mjs",
10+
}
11+
12+
const reverseMapping = {
13+
".js": ".ts",
14+
".cjs": ".cts",
15+
".mjs": ".mts",
16+
}
17+
18+
/**
19+
* Maps the typescript file extension that should be added in an import statement,
20+
* based on the given file extension of the referenced file OR fallsback to the original given extension.
21+
*
22+
* For example, in typescript, when referencing another typescript from a typescript file,
23+
* a .js extension should be used instead of the original .ts extension of the referenced file.
24+
*
25+
* @param {RuleContext} context
26+
* @param {string} filePath The filePath of the import
27+
* @param {string} fallbackExtension The non-typescript fallback
28+
* @param {boolean} reverse Execute a reverse path mapping
29+
* @returns {string} The file extension to append to the import statement.
30+
*/
31+
module.exports = function mapTypescriptExtension(
32+
context,
33+
filePath,
34+
fallbackExtension,
35+
reverse = false
36+
) {
37+
const ext = path.extname(filePath)
38+
if (reverse) {
39+
if (isTypescript(context) && ext in reverseMapping) {
40+
return reverseMapping[ext]
41+
}
42+
} else {
43+
if (isTypescript(context) && ext in mapping) {
44+
return mapping[ext]
45+
}
46+
}
47+
48+
return fallbackExtension
49+
}

tests/fixtures/no-missing/d.ts

Whitespace-only changes.

tests/lib/rules/no-missing-import.js

+13
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ ruleTester.run("no-missing-import", rule, {
5858
filename: fixture("test.js"),
5959
code: "import a from './a.js';",
6060
},
61+
{
62+
filename: fixture("test.ts"),
63+
code: "import a from './a.js';",
64+
},
65+
{
66+
filename: fixture("test.ts"),
67+
code: "import a from './d.js';",
68+
},
6169
{
6270
filename: fixture("test.js"),
6371
code: "import aConfig from './a.config.js';",
@@ -166,6 +174,11 @@ ruleTester.run("no-missing-import", rule, {
166174
code: "import c from './c';",
167175
errors: ['"./c" is not found.'],
168176
},
177+
{
178+
filename: fixture("test.ts"),
179+
code: "import d from './d';",
180+
errors: ['"./d" is not found.'],
181+
},
169182
{
170183
filename: fixture("test.js"),
171184
code: "import d from './d';",

0 commit comments

Comments
 (0)