123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- /**
- * @author Toru Nagashima
- * See LICENSE file in root directory for full license.
- */
- "use strict"
- const path = require("path")
- const fs = require("fs")
- const getTryExtensions = require("../util/get-try-extensions")
- 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/mysticatea/eslint-plugin-node/blob/v11.1.0/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 visitImport(context, { optionIndex: 1 }, targets => {
- targets.forEach(verify)
- })
- },
- }
|