Socket
Socket
Sign inDemoInstall

eslint-plugin-n

Package Overview
Dependencies
10
Maintainers
2
Versions
55
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 16.6.2 to 17.0.0-0

73

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

@@ -18,10 +18,10 @@ /**

function getExistingExtensions(filePath) {
const basename = path.basename(filePath, path.extname(filePath))
const directory = path.dirname(filePath)
const extension = path.extname(filePath)
const basename = path.basename(filePath, extension)
try {
return fs
.readdirSync(path.dirname(filePath))
.filter(
filename =>
path.basename(filename, path.extname(filename)) === basename
)
.readdirSync(directory)
.filter(filename => filename.startsWith(`${basename}.`))
.map(filename => path.extname(filename))

@@ -78,43 +78,52 @@ } catch (_error) {

// Get extension.
const originalExt = path.extname(name)
const existingExts = getExistingExtensions(filePath)
const ext = path.extname(filePath) || existingExts.join(" or ")
const style = overrideStyle[ext] || defaultStyle
const currentExt = path.extname(name)
const actualExt = path.extname(filePath)
const style = overrideStyle[actualExt] || defaultStyle
const expectedExt = mapTypescriptExtension(
context,
filePath,
actualExt
)
// Verify.
if (style === "always" && ext !== originalExt) {
const fileExtensionToAdd = mapTypescriptExtension(
context,
filePath,
ext
)
if (style === "always" && currentExt !== expectedExt) {
context.report({
node,
messageId: "requireExt",
data: { ext: fileExtensionToAdd },
data: { ext: expectedExt },
fix(fixer) {
if (existingExts.length !== 1) {
return null
}
const index = node.range[1] - 1
return fixer.insertTextBeforeRange(
[index, index],
fileExtensionToAdd
expectedExt
)
},
})
} else if (style === "never" && ext === originalExt) {
}
if (
style === "never" &&
currentExt !== "" &&
expectedExt !== "" &&
currentExt === expectedExt
) {
const otherExtensions = getExistingExtensions(filePath)
let fix = fixer => {
const index = name.lastIndexOf(currentExt)
const start = node.range[0] + 1 + index
const end = start + currentExt.length
return fixer.removeRange([start, end])
}
if (otherExtensions.length > 1) {
fix = undefined
}
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])
},
data: { ext: currentExt },
fix,
})

@@ -121,0 +130,0 @@ }

@@ -12,7 +12,2 @@ /**

const path = require("path")
const resolve = require("resolve")
const { pathToFileURL, fileURLToPath } = require("url")
const {
defaultResolve: importResolve,
} = require("../converted-esm/import-meta-resolve")
const getPackageJson = require("../util/get-package-json")

@@ -33,3 +28,4 @@ const mergeVisitorsInPlace = require("../util/merge-visitors-in-place")

"dns",
/* "domain", */ "events",
/* "domain", */
"events",
"fs",

@@ -42,3 +38,4 @@ "http",

"path",
/* "punycode", */ "querystring",
/* "punycode", */
"querystring",
"readline",

@@ -138,18 +135,4 @@ "repl",

let resolved = ""
const moduleId = `${name}/`
try {
resolved = resolve.sync(moduleId, {
basedir: dirPath,
})
} catch (_error) {
try {
const { url } = importResolve(moduleId, {
parentURL: pathToFileURL(dirPath).href,
})
resolved = fileURLToPath(url)
} catch (_error) {
continue
}
if (target.filePath == null) {
continue
}

@@ -163,3 +146,3 @@

name: path
.relative(dirPath, resolved)
.relative(dirPath, target.filePath)
.replace(BACK_SLASH, "/"),

@@ -166,0 +149,0 @@ },

@@ -14,2 +14,17 @@ /**

/**
* Reports a missing file from ImportTarget
* @param {RuleContext} context - A context to report.
* @param {import('../util/import-target.js')} target - A list of target information to check.
* @returns {void}
*/
function markMissing(context, target) {
context.report({
node: target.node,
loc: target.node.loc,
messageId: "notFound",
data: target,
})
}
/**
* Checks whether or not each requirement target exists.

@@ -21,3 +36,3 @@ *

* @param {RuleContext} context - A context to report.
* @param {ImportTarget[]} targets - A list of target information to check.
* @param {import('../util/import-target.js')[]} targets - A list of target information to check.
* @returns {void}

@@ -29,10 +44,22 @@ */

for (const target of targets) {
const missingModule =
if (
target.moduleName != null &&
!allowed.has(target.moduleName) &&
target.filePath == null
) {
markMissing(context, target)
continue
}
let missingFile = target.moduleName == null && !exists(target.filePath)
if (target.moduleName != null) {
continue
}
let missingFile =
target.filePath == null ? false : !exists(target.filePath)
if (missingFile && isTypescript(context)) {
const parsed = path.parse(target.filePath)
const pathWithoutExt = path.resolve(parsed.dir, parsed.name)
const reversedExts = mapTypescriptExtension(

@@ -45,4 +72,3 @@ context,

const reversedPaths = reversedExts.map(
reversedExt =>
path.resolve(parsed.dir, parsed.name) + reversedExt
reversedExt => pathWithoutExt + reversedExt
)

@@ -54,9 +80,5 @@ missingFile = reversedPaths.every(

}
if (missingModule || missingFile) {
context.report({
node: target.node,
loc: target.node.loc,
messageId: "notFound",
data: target,
})
if (missingFile) {
markMissing(context, target)
}

@@ -63,0 +85,0 @@ }

@@ -62,3 +62,3 @@ /**

}
const relativeTargetPath = toRelative(target.filePath)
const relativeTargetPath = toRelative(target.filePath ?? "")
return (

@@ -74,2 +74,3 @@ relativeTargetPath !== "" &&

!allowed.has(target.moduleName)
if (isPrivateFile() || isDevPackage()) {

@@ -76,0 +77,0 @@ context.report({

@@ -41,2 +41,6 @@ /**

module.exports = function exists(filePath) {
if (filePath == null) {
return false
}
let result = cache.get(filePath)

@@ -43,0 +47,0 @@ if (result == null) {

@@ -7,4 +7,23 @@ /**

const DEFAULT_VALUE = Object.freeze([".js", ".json", ".node"])
const { getTSConfigForContext } = require("./get-tsconfig")
const isTypescript = require("./is-typescript")
const DEFAULT_JS_VALUE = Object.freeze([
".js",
".json",
".node",
".mjs",
".cjs",
])
const DEFAULT_TS_VALUE = Object.freeze([
".js",
".ts",
".mjs",
".mts",
".cjs",
".cts",
".json",
".node",
])
/**

@@ -17,3 +36,3 @@ * Gets `tryExtensions` property from a given option object.

function get(option) {
if (option && option.tryExtensions && Array.isArray(option.tryExtensions)) {
if (Array.isArray(option?.tryExtensions)) {
return option.tryExtensions.map(String)

@@ -29,3 +48,3 @@ }

* 2. This checks `settings.n` | `settings.node` property, then returns it if exists.
* 3. This returns `[".js", ".json", ".node"]`.
* 3. This returns `[".js", ".json", ".node", ".mjs", ".cjs"]`.
*

@@ -36,9 +55,19 @@ * @param {RuleContext} context - The rule context.

module.exports = function getTryExtensions(context, optionIndex = 0) {
return (
get(context.options && context.options[optionIndex]) ||
get(
context.settings && (context.settings.n || context.settings.node)
) ||
DEFAULT_VALUE
)
const configured =
get(context.options?.[optionIndex]) ??
get(context.settings?.n) ??
get(context.settings?.node)
if (configured != null) {
return configured
}
if (isTypescript(context)) {
const tsconfig = getTSConfigForContext(context)
if (tsconfig?.config?.compilerOptions?.allowImportingTsExtensions) {
return DEFAULT_TS_VALUE
}
}
return DEFAULT_JS_VALUE
}

@@ -45,0 +74,0 @@

@@ -20,3 +20,3 @@ "use strict"

* @param {string} filename - The path to the file we need to find the tsconfig.json of
* @returns {import("get-tsconfig").TsConfigResult}
* @returns {import("get-tsconfig").TsConfigResult | null}
*/

@@ -27,7 +27,25 @@ function getTSConfigForFile(filename) {

/**
* Attempts to get the ExtensionMap from the tsconfig of a given file.
*
* @param {import('eslint').Rule.RuleContext} context - The current eslint context
* @returns {import("get-tsconfig").TsConfigResult | null}
*/
function getTSConfigForContext(context) {
// TODO: remove context.get(PhysicalFilename|Filename) when dropping eslint < v10
const filename =
context.physicalFilename ??
context.getPhysicalFilename?.() ??
context.filename ??
context.getFilename?.()
return getTSConfigForFile(filename)
}
module.exports = {
getTSConfig,
getTSConfigForFile,
getTSConfigForContext,
}
module.exports.schema = { type: "string" }
"use strict"
const { getTSConfig, getTSConfigForFile } = require("./get-tsconfig")
const { getTSConfig, getTSConfigForContext } = require("./get-tsconfig")

@@ -35,5 +35,10 @@ const DEFAULT_MAPPING = normalise([

/**
* @param {Record<string, string>} typescriptExtensionMap A forward extension mapping
* @returns {ExtensionMap}
*/
function normalise(typescriptExtensionMap) {
const forward = {}
const backward = {}
for (const [typescript, javascript] of typescriptExtensionMap) {

@@ -47,2 +52,3 @@ forward[typescript] = javascript

}
return { forward, backward }

@@ -94,7 +100,7 @@ }

*
* @param {string} filename - The filename we're getting from
* @param {import('eslint').Rule.RuleContext} context - The current file context
* @returns {ExtensionMap} The `typescriptExtensionMap` value, or `null`.
*/
function getFromTSConfigFromFile(filename) {
return getMappingFromTSConfig(getTSConfigForFile(filename)?.config)
function getFromTSConfigFromFile(context) {
return getMappingFromTSConfig(getTSConfigForContext(context)?.config)
}

@@ -115,14 +121,9 @@

* @param {import("eslint").Rule.RuleContext} context - The rule context.
* @returns {string[]} A list of extensions.
* @returns {ExtensionMap} A list of extensions.
*/
module.exports = function getTypescriptExtensionMap(context) {
const filename =
context.physicalFilename ??
context.getPhysicalFilename?.() ??
context.filename ??
context.getFilename?.() // TODO: remove context.get(PhysicalFilename|Filename) when dropping eslint < v10
return (
get(context.options?.[0]) ||
get(context.settings?.n ?? context.settings?.node) ||
getFromTSConfigFromFile(filename) ||
getFromTSConfigFromFile(context) ||
PRESERVE_MAPPING

@@ -129,0 +130,0 @@ )

@@ -7,94 +7,53 @@ /**

const path = require("path")
const { pathToFileURL, fileURLToPath } = require("url")
require("util").inspect.defaultOptions.depth = null
const { resolve } = require("path")
const isBuiltin = require("is-builtin-module")
const resolve = require("resolve")
const {
defaultResolve: importResolve,
} = require("../converted-esm/import-meta-resolve")
const resolver = require("enhanced-resolve")
const isTypescript = require("./is-typescript")
const { getTSConfigForContext } = require("./get-tsconfig.js")
const getTypescriptExtensionMap = require("./get-typescript-extension-map")
function removeTrailWildcard(input) {
if (Array.isArray(input)) {
return [...input].map(removeTrailWildcard)
}
return input.replace(/[/\\*]+$/, "")
}
/**
* Resolve the given id to file paths.
* @param {boolean} isModule The flag which indicates this id is a module.
* @param {string} id The id to resolve.
* @param {object} options The options of node-resolve module.
* It requires `options.basedir`.
* @param {'import' | 'require'} moduleType - whether the target was require-ed or imported
* @returns {string|null} The resolved path.
* Initialize this instance.
* @param {import('eslint').Rule.RuleContext} context - The context for the import origin.
* @returns {import('enhanced-resolve').ResolveOptions['alias'] | undefined}
*/
function getFilePath(isModule, id, options, moduleType) {
if (moduleType === "import") {
const paths =
options.paths && options.paths.length > 0
? options.paths.map(p => path.resolve(process.cwd(), p))
: [options.basedir]
for (const aPath of paths) {
try {
const { url } = importResolve(id, {
parentURL: pathToFileURL(path.join(aPath, "dummy-file.mjs"))
.href,
conditions: ["node", "import", "require"],
})
function getTSConfigAliases(context) {
const tsConfig = getTSConfigForContext(context)
if (url) {
return fileURLToPath(url)
}
} catch (e) {
continue
}
}
const paths = tsConfig?.config?.compilerOptions?.paths
if (isModule) {
return null
}
return path.resolve(
(options.paths && options.paths[0]) || options.basedir,
id
)
} else {
try {
return resolve.sync(id, options)
} catch (_err) {
try {
const { url } = importResolve(id, {
parentURL: pathToFileURL(
path.join(options.basedir, "dummy-file.js")
).href,
conditions: ["node", "require"],
})
return fileURLToPath(url)
} catch (err) {
if (isModule) {
return null
}
return path.resolve(options.basedir, id)
}
}
if (paths == null) {
return
}
}
function isNodeModule(name, options) {
try {
return require.resolve(name, options).startsWith(path.sep)
} catch {
return false
}
return Object.entries(paths).map(([name, alias]) => ({
name: removeTrailWildcard(name),
alias: removeTrailWildcard(alias),
}))
}
/**
* Gets the module name of a given path.
*
* e.g. `eslint/lib/ast-utils` -> `eslint`
*
* @param {string} nameOrPath - A path to get.
* @returns {string} The module name of the path.
* @typedef {Object} Options
* @property {string[]} [extensions]
* @property {string[]} [paths]
* @property {string} basedir
*/
function getModuleName(nameOrPath) {
let end = nameOrPath.indexOf("/")
if (end !== -1 && nameOrPath[0] === "@") {
end = nameOrPath.indexOf("/", 1 + end)
}
/**
* @typedef { 'unknown' | 'relative' | 'absolute' | 'node' | 'npm' | 'http' } ModuleType
* @typedef { 'import' | 'require' | 'type' } ModuleStyle
*/
return end === -1 ? nameOrPath : nameOrPath.slice(0, end)
function trimAfter(string, matcher, count = 1) {
return string.split(matcher).slice(0, count).join(matcher)
}

@@ -108,13 +67,18 @@

* Initialize this instance.
* @param {ASTNode} node - The node of a `require()` or a module declaraiton.
* @param {import('eslint').Rule.RuleContext} context - The context for the import origin.
* @param {import('eslint').Rule.Node} node - The node of a `require()` or a module declaraiton.
* @param {string} name - The name of an import target.
* @param {object} options - The options of `node-resolve` module.
* @param {Options} options - The options of `enhanced-resolve` module.
* @param {'import' | 'require'} moduleType - whether the target was require-ed or imported
*/
constructor(node, name, options, moduleType) {
const isModule = !/^(?:[./\\]|\w+:)/u.test(name)
constructor(context, node, name, options, moduleType) {
/**
* The context for the import origin
* @type {import('eslint').Rule.Node}
*/
this.context = context
/**
* The node of a `require()` or a module declaraiton.
* @type {ASTNode}
* @type {import('eslint').Rule.Node}
*/

@@ -130,25 +94,18 @@ this.node = node

/**
* What type of module is this
* @type {'unknown'|'relative'|'absolute'|'node'|'npm'|'http'|void}
* The import target options.
* @type {Options}
*/
this.moduleType = "unknown"
this.options = options
if (name.startsWith("./") || name.startsWith(".\\")) {
this.moduleType = "relative"
} else if (name.startsWith("/") || name.startsWith("\\")) {
this.moduleType = "absolute"
} else if (isBuiltin(name)) {
this.moduleType = "node"
} else if (isNodeModule(name, options)) {
this.moduleType = "npm"
} else if (name.startsWith("http://") || name.startsWith("https://")) {
this.moduleType = "http"
}
/**
* What type of module are we looking for?
* @type {ModuleType}
*/
this.moduleType = this.getModuleType()
/**
* The full path of this import target.
* If the target is a module and it does not exist then this is `null`.
* @type {string|null}
* What import style are we using
* @type {ModuleStyle}
*/
this.filePath = getFilePath(isModule, name, options, moduleType)
this.moduleStyle = this.getModuleStyle(moduleType)

@@ -158,6 +115,175 @@ /**

* If the target is a relative path then this is `null`.
* @type {string|null}
* @type {string | null}
*/
this.moduleName = isModule ? getModuleName(name) : null
this.moduleName = this.getModuleName()
/**
* The full path of this import target.
* If the target is a module and it does not exist then this is `null`.
* @type {string | null}
*/
this.filePath = this.getFilePath()
}
/**
* What type of module is this
* @returns {ModuleType}
*/
getModuleType() {
if (/^\.{1,2}([\\/]|$)/.test(this.name)) {
return "relative"
}
if (/^[\\/]/.test(this.name)) {
return "absolute"
}
if (isBuiltin(this.name)) {
return "node"
}
if (/^(@[\w~-][\w.~-]*\/)?[\w~-][\w.~-]*/.test(this.name)) {
return "npm"
}
if (/^https?:\/\//.test(this.name)) {
return "http"
}
return "unknown"
}
/**
* What module import style is used
* @param {'import' | 'require'} fallback
* @returns {ModuleStyle}
*/
getModuleStyle(fallback) {
/** @type {import('eslint').Rule.Node} */
let node = { parent: this.node }
do {
node = node.parent
// `const {} = require('')`
if (
node.type === "CallExpression" &&
node.callee.name === "require"
) {
return "require"
}
// `import type {} from '';`
if (
node.type === "ImportDeclaration" &&
node.importKind === "type"
) {
return "type"
}
// `import {} from '';`
if (
node.type === "ImportDeclaration" &&
node.importKind === "value"
) {
return "import"
}
} while (node.parent)
return fallback
}
/**
* Get the node or npm module name
* @returns {string}
*/
getModuleName() {
if (this.moduleType === "relative") return
if (this.moduleType === "npm") {
if (this.name.startsWith("@")) {
return trimAfter(this.name, "/", 2)
}
return trimAfter(this.name, "/")
}
if (this.moduleType === "node") {
if (this.name.startsWith("node:")) {
return trimAfter(this.name.slice(5), "/")
}
return trimAfter(this.name, "/")
}
}
getPaths() {
if (Array.isArray(this.options.paths)) {
return [...this.options.paths, this.options.basedir]
}
return [this.options.basedir]
}
/**
* Resolve the given id to file paths.
* @returns {string | null} The resolved path.
*/
getFilePath() {
const conditionNames = ["node", "require"]
const { extensions } = this.options
const mainFields = []
const mainFiles = []
if (this.moduleStyle === "import") {
conditionNames.push("import")
}
if (this.moduleStyle === "type") {
conditionNames.push("import", "types")
}
if (
this.moduleStyle === "require" ||
this.moduleType === "npm" ||
this.moduleType === "node"
) {
mainFields.push("main")
mainFiles.push("index")
}
let alias = undefined
let extensionAlias = undefined
if (isTypescript(this.context)) {
alias = getTSConfigAliases(this.context)
extensionAlias = getTypescriptExtensionMap(this.context).backward
}
const requireResolve = resolver.create.sync({
conditionNames,
extensions,
mainFields,
mainFiles,
extensionAlias,
alias,
})
const cwd = this.context.settings?.cwd ?? process.cwd()
for (const directory of this.getPaths()) {
try {
const baseDir = resolve(cwd, directory)
return requireResolve(baseDir, this.name)
} catch {
continue
}
}
if (this.moduleType === "absolute" || this.moduleType === "relative") {
return resolve(this.options.basedir, this.name)
}
return null
}
}

@@ -71,3 +71,9 @@ /**

targets.push(
new ImportTarget(sourceNode, name, options, "import")
new ImportTarget(
context,
sourceNode,
name,
options,
"import"
)
)

@@ -74,0 +80,0 @@ }

@@ -63,3 +63,9 @@ /**

targets.push(
new ImportTarget(targetNode, name, options, "require")
new ImportTarget(
context,
targetNode,
name,
options,
"require"
)
)

@@ -66,0 +72,0 @@ }

{
"name": "eslint-plugin-n",
"version": "16.6.2",
"version": "17.0.0-0",
"description": "Additional ESLint's rules for Node.js",
"engines": {
"node": ">=16.0.0"
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},

@@ -14,7 +14,7 @@ "main": "lib/index.js",

"peerDependencies": {
"eslint": ">=7.0.0"
"eslint": ">=8.23.0"
},
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"builtins": "^5.0.1",
"enhanced-resolve": "^5.15.0",
"eslint-plugin-es-x": "^7.5.0",

@@ -27,3 +27,2 @@ "get-tsconfig": "^4.7.0",

"minimatch": "^3.1.2",
"resolve": "^1.22.2",
"semver": "^7.5.3"

@@ -35,4 +34,3 @@ },

"@typescript-eslint/parser": "^5.60.0",
"esbuild": "^0.18.7",
"eslint": "^8.56.0",
"eslint": "^8",
"eslint-config-prettier": "^8.8.0",

@@ -44,3 +42,2 @@ "eslint-doc-generator": "^1.6.1",

"husky": "^8.0.3",
"import-meta-resolve": "^3.0.0",
"lint-staged": "^13.2.2",

@@ -118,3 +115,6 @@ "markdownlint-cli": "^0.35.0",

"*.{json,js}": "prettier --write --ignore-path .eslintignore"
},
"imports": {
"#eslint-rule-tester": "./tests/eslint-rule-tester.js"
}
}

@@ -21,4 +21,7 @@ # eslint-plugin-n

- Requires Node.js `>=16.0.0`
- Requires ESLint `>=7.0.0`
| Version | Supported Node.js | Supported ESLint Version |
|---------|-------------------|---------------------------|
| 17.x | `^18.18.0 \|\| ^20.9.0 \|\| >=21.1.0` | `>=8.23.0` |
| 16.x | `>=16.0.0` | `>=7.0.0` |
| 15.x | `>=12.22.0` | `>=7.0.0` |

@@ -25,0 +28,0 @@ **Note:** It recommends a use of [the "engines" field of package.json](https://docs.npmjs.com/files/package.json#engines). The "engines" field is used by `n/no-unsupported-features/*` rules.

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc