forked from eslint-community/eslint-plugin-n
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfile-extension-in-import.js
130 lines (123 loc) · 4.54 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
/**
* @author Toru Nagashima
* See LICENSE file in root directory for full license.
*/
"use strict"
const path = require("path")
const fs = require("fs")
const mapTypescriptExtension = require("../util/map-typescript-extension.js")
const visitImport = require("../util/visit-import")
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/weiran-zsd/eslint-plugin-node/blob/HEAD/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: {},
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 existingExts = getExistingExtensions(filePath)
const ext = path.extname(filePath) || existingExts.join(" or ")
const style = overrideStyle[ext] || defaultStyle
// Verify.
if (style === "always" && ext !== originalExt) {
const fileExtensionToAdd = mapTypescriptExtension(
context,
filePath,
ext
)
context.report({
node,
messageId: "requireExt",
data: { ext: fileExtensionToAdd },
fix(fixer) {
if (existingExts.length !== 1) {
return null
}
const index = node.range[1] - 1
return fixer.insertTextBeforeRange(
[index, index],
fileExtensionToAdd
)
},
})
} 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 visitImport(context, { optionIndex: 1 }, targets => {
targets.forEach(verify)
})
},
}