/
file-extension-in-import.js
132 lines (125 loc) · 4.65 KB
/
file-extension-in-import.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
* @author Toru Nagashima
* See LICENSE file in root directory for full license.
*/
"use strict"
const path = require("path")
const fs = require("fs")
const getImportExportTargets = require("../util/get-import-export-targets")
const getTryExtensions = require("../util/get-try-extensions")
const packageNamePattern = /^(?:@[^/\\]+[/\\])?[^/\\]+$/u
const corePackageOverridePattern = /^(?: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
/**
* Get all file extensions of the files which have the same basename.
* @param {string} filePath The path to the original file to check.
* @returns {string[]} File extensions.
*/
function getExistingExtensions(filePath) {
const basename = path.basename(filePath, path.extname(filePath))
try {
return fs
.readdirSync(path.dirname(filePath))
.filter(
filename =>
path.basename(filename, path.extname(filename)) === basename
)
.map(filename => path.extname(filename))
} catch (_error) {
return []
}
}
module.exports = {
meta: {
docs: {
description:
"enforce the style of file extensions in `import` declarations",
category: "Stylistic Issues",
recommended: false,
url:
"https://github.com/mysticatea/eslint-plugin-node/blob/v9.0.1/docs/rules/file-extension-in-import.md",
},
fixable: "code",
messages: {
requireExt: "require file extension '{{ext}}'.",
forbidExt: "forbid file extension '{{ext}}'.",
},
schema: [
{
enum: ["always", "never"],
},
{
type: "object",
properties: {
tryExtensions: getTryExtensions.schema,
},
additionalProperties: {
enum: ["always", "never"],
},
},
],
type: "suggestion",
},
create(context) {
if (context.getFilename().startsWith("<")) {
return {}
}
const defaultStyle = context.options[0] || "always"
const overrideStyle = context.options[1] || {}
function verify({ filePath, name, node }) {
// Ignore if it's not resolved to a file or it's a bare module.
if (
!filePath ||
packageNamePattern.test(name) ||
corePackageOverridePattern.test(name)
) {
return
}
// Get extension.
const originalExt = path.extname(name)
const resolvedExt = path.extname(filePath)
const existingExts = getExistingExtensions(filePath)
if (!resolvedExt && existingExts.length !== 1) {
// Ignore if the file extension could not be determined one.
return
}
const ext = resolvedExt || existingExts[0]
const style = overrideStyle[ext] || defaultStyle
// Verify.
if (style === "always" && ext !== originalExt) {
context.report({
node,
messageId: "requireExt",
data: { ext },
fix(fixer) {
if (existingExts.length !== 1) {
return null
}
const index = node.range[1] - 1
return fixer.insertTextBeforeRange([index, index], ext)
},
})
} else if (style === "never" && ext === originalExt) {
context.report({
node,
messageId: "forbidExt",
data: { ext },
fix(fixer) {
if (existingExts.length !== 1) {
return null
}
const index = name.lastIndexOf(ext)
const start = node.range[0] + 1 + index
const end = start + ext.length
return fixer.removeRange([start, end])
},
})
}
}
return {
"Program:exit"(node) {
const opts = { optionIndex: 1 }
getImportExportTargets(context, node, opts).forEach(verify)
},
}
},
}