Skip to content

Commit

Permalink
feat!: Start using enhanced-resolve to improve ts support (#139)
Browse files Browse the repository at this point in the history
* feat: Use enhanced-resolve for imports

* chore: Improve the metadata from "ImportTarget"

* chore: remove "enhanced-resolve" from "no-hide-core-modules"

* test: Add a test for #66

* feat!: Allow ts paths aliases (#84)

* feat: Allow for "allowImportingTsExtensions" (#134)

* feat: Add test for import maps (#147)

* Update lib/util/import-target.js

Co-authored-by: Sebastian Good <[email protected]>

* test: Add test for n/no-missing-require eslint/use-at-your-own-risk

* chore: Remove esbuild

* feat: Allow for settings.cwd to be used before process.cwd

* chore: replace reference to eslint/use-at-your-own-risk

* chore: Remove more unused packages

* chore: update rule test options to flat config

* fix: incorrect env in tests

---------

Co-authored-by: 唯然 <[email protected]>
  • Loading branch information
scagood and aladdin-add authored Jan 9, 2024
1 parent 5449752 commit dc9f473
Show file tree
Hide file tree
Showing 31 changed files with 492 additions and 1,499 deletions.
1,274 changes: 0 additions & 1,274 deletions lib/converted-esm/import-meta-resolve.js

This file was deleted.

73 changes: 41 additions & 32 deletions lib/rules/file-extension-in-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ const visitImport = require("../util/visit-import")
* @returns {string[]} File extensions.
*/
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))
} catch (_error) {
return []
Expand Down Expand Up @@ -74,47 +74,56 @@ module.exports = {
}

// 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,
})
}
}
Expand Down
31 changes: 7 additions & 24 deletions lib/rules/no-hide-core-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@
"use strict"

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")
const mergeVisitorsInPlace = require("../util/merge-visitors-in-place")
const visitImport = require("../util/visit-import")
Expand All @@ -29,15 +24,17 @@ const CORE_MODULES = new Set([
"crypto",
"dgram",
"dns",
/* "domain", */ "events",
/* "domain", */
"events",
"fs",
"http",
"https",
"module",
"net",
"os",
"path",
/* "punycode", */ "querystring",
/* "punycode", */
"querystring",
"readline",
"repl",
"stream",
Expand Down Expand Up @@ -132,22 +129,8 @@ module.exports = {
continue
}

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
}

context.report({
Expand All @@ -156,7 +139,7 @@ module.exports = {
messageId: "unexpectedImport",
data: {
name: path
.relative(dirPath, resolved)
.relative(dirPath, target.filePath)
.replace(BACK_SLASH, "/"),
},
})
Expand Down
46 changes: 34 additions & 12 deletions lib/util/check-existence.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,50 +10,72 @@ const getAllowModules = require("./get-allow-modules")
const isTypescript = require("./is-typescript")
const mapTypescriptExtension = require("../util/map-typescript-extension")

/**
* 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.
*
* It looks up the target according to the logic of Node.js.
* See Also: https://nodejs.org/api/modules.html
*
* @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}
*/
exports.checkExistence = function checkExistence(context, targets) {
const allowed = new Set(getAllowModules(context))

for (const target of targets) {
const missingModule =
if (
target.moduleName != null &&
!allowed.has(target.moduleName) &&
target.filePath == null
) {
markMissing(context, target)
continue
}

if (target.moduleName != null) {
continue
}

let missingFile =
target.filePath == null ? false : !exists(target.filePath)

let missingFile = target.moduleName == null && !exists(target.filePath)
if (missingFile && isTypescript(context)) {
const parsed = path.parse(target.filePath)
const pathWithoutExt = path.resolve(parsed.dir, parsed.name)

const reversedExts = mapTypescriptExtension(
context,
target.filePath,
parsed.ext,
true
)
const reversedPaths = reversedExts.map(
reversedExt =>
path.resolve(parsed.dir, parsed.name) + reversedExt
reversedExt => pathWithoutExt + reversedExt
)
missingFile = reversedPaths.every(
reversedPath =>
target.moduleName == null && !exists(reversedPath)
)
}
if (missingModule || missingFile) {
context.report({
node: target.node,
loc: target.node.loc,
messageId: "notFound",
data: target,
})

if (missingFile) {
markMissing(context, target)
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion lib/util/check-publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ exports.checkPublish = function checkPublish(context, filePath, targets) {
if (target.moduleName != null) {
return false
}
const relativeTargetPath = toRelative(target.filePath)
const relativeTargetPath = toRelative(target.filePath ?? "")
return (
relativeTargetPath !== "" &&
npmignore.match(relativeTargetPath)
Expand All @@ -70,6 +70,7 @@ exports.checkPublish = function checkPublish(context, filePath, targets) {
devDependencies.has(target.moduleName) &&
!dependencies.has(target.moduleName) &&
!allowed.has(target.moduleName)

if (isPrivateFile() || isDevPackage()) {
context.report({
node: target.node,
Expand Down
4 changes: 4 additions & 0 deletions lib/util/exists.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ function existsCaseSensitive(filePath) {
* @returns {boolean} `true` if the file of a given path exists.
*/
module.exports = function exists(filePath) {
if (filePath == null) {
return false
}

let result = cache.get(filePath)
if (result == null) {
try {
Expand Down
49 changes: 39 additions & 10 deletions lib/util/get-try-extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,26 @@
*/
"use strict"

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",
])

/**
* Gets `tryExtensions` property from a given option object.
Expand All @@ -13,7 +32,7 @@ const DEFAULT_VALUE = Object.freeze([".js", ".json", ".node"])
* @returns {string[]|null} The `tryExtensions` value, or `null`.
*/
function get(option) {
if (option && option.tryExtensions && Array.isArray(option.tryExtensions)) {
if (Array.isArray(option?.tryExtensions)) {
return option.tryExtensions.map(String)
}
return null
Expand All @@ -24,19 +43,29 @@ function get(option) {
*
* 1. This checks `options` property, then returns it if exists.
* 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"]`.
*
* @param {RuleContext} context - The rule context.
* @returns {string[]} A list of extensions.
*/
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
}

module.exports.schema = {
Expand Down
20 changes: 19 additions & 1 deletion lib/util/get-tsconfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,33 @@ function getTSConfig(filename) {
* Attempts to get the ExtensionMap from the tsconfig of a given file.
*
* @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}
*/
function getTSConfigForFile(filename) {
return getTsconfig(filename, "tsconfig.json", fsCache)
}

/**
* 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" }
Loading

0 comments on commit dc9f473

Please sign in to comment.