Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@jsenv/importmap-eslint-resolver

Package Overview
Dependencies
Maintainers
2
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jsenv/importmap-eslint-resolver - npm Package Compare versions

Comparing version 2.4.2 to 3.0.0

src/internal/readImportMapFromFile.js

15

package.json
{
"name": "@jsenv/importmap-eslint-resolver",
"version": "2.4.2",
"version": "3.0.0",
"description": "importmap resolution for eslint.",

@@ -11,6 +11,7 @@ "license": "MIT",

"engines": {
"node": ">=13.0.0"
"node": ">=14.9.0"
},
"publishConfig": {
"access": "public"
"access": "public",
"registry": "https://registry.npmjs.org"
},

@@ -45,3 +46,3 @@ "type": "module",

"dependencies": {
"@jsenv/import-map": "6.13.0",
"@jsenv/import-map": "6.13.2",
"@jsenv/logger": "4.0.0",

@@ -53,11 +54,11 @@ "@jsenv/util": "4.0.6"

"@jsenv/codecov-upload": "3.4.3",
"@jsenv/core": "17.9.0",
"@jsenv/core": "17.10.0",
"@jsenv/eslint-config": "12.9.1",
"@jsenv/github-release-package": "1.2.2",
"@jsenv/node-module-import-map": "13.0.0",
"@jsenv/node-module-import-map": "13.4.1",
"@jsenv/package-publish": "1.5.2",
"@jsenv/prettier-check-project": "5.6.1",
"eslint": "7.22.0",
"eslint": "7.25.0",
"prettier": "2.2.1"
}
}

@@ -14,8 +14,5 @@ # importmap-eslint-resolver

- [Installation](#installation)
- [Install eslint-plugin-import](#install-eslint-plugin-import)
- [Install importmap-eslint-resolver](#install-importmap-eslint-resolver)
- [Configure eslint](#configure-eslint)
- [Set importmap file path](#Set-importmap-file-path)
- [Bare specifier](#Bare-specifier)
- [Extensionless import](#extensionless-import)
- [About resolution](#About-resolution)
- [Configuration](#Configuration)
- [Advanced configuration example](#Advanced-configuration-example)

@@ -57,5 +54,6 @@ # Presentation

## Install eslint-plugin-import
<details>
<summary>Install eslint-plugin-import</summary>
If you already use this ESLint plugin you can skip this step.
> If you already use this ESLint plugin you can skip this step.

@@ -66,4 +64,7 @@ ```console

## Install importmap-eslint-resolver
</details>
<details>
<summary>Install importmap-eslint-resolver</summary>
```console

@@ -73,9 +74,15 @@ npm install --save-dev @jsenv/importmap-eslint-resolver

## Configure eslint
</details>
- Your eslint config must enable `eslint-plugin-import`
- Your eslint config must use `@jsenv/importmap-eslint-resolver` resolver
<details>
<summary>Configure ESLint</summary>
It means your minimal `.eslintrc.cjs` file looks like this:
Your ESLint config must:
- enable `eslint-plugin-import` in `plugins`
- configure `eslint-plugin-import` to use `@jsenv/importmap-eslint-resolver` as resolver
- configure `projectDirectoryUrl` and `importMapFileRelativeUrl`
Your minimal `.eslintrc.cjs` file looks like this:
```js

@@ -88,2 +95,3 @@ module.exports = {

projectDirectoryUrl: __dirname,
importMapFileRelativeUrl: "./project.importmap",
},

@@ -95,36 +103,46 @@ },

# Set importmap file path
</details>
By default we will search for a file in your project directory named `import-map.importmap`. If the importmap file is located somewhere else you can use `importMapFileRelativeUrl` parameter to tell us where to look at.
# About resolution
**By default** the resolution is:
- case sensitive
- browser like
- consider node core modules (fs, url) as not found
- do not implement node module resolution
- do not understand path without extension (does not try to auto add extension)
This resolution **default** behaviour is documented in this section and **can be configured to your convenience**.
## Case sensitivity
This resolver is case sensitive by default: An import is found only if the import path and actual file on the filesystem have same case.
```js
module.exports = {
plugins: ["import"],
settings: {
"import/resolver": {
[require.resolve("@jsenv/importmap-eslint-resolver")]: {
projectDirectoryUrl: __dirname,
importMapFileRelativeUrl: "./project.importmap",
},
},
},
}
import { getUser } from "./getUser.js"
```
# Bare specifier
The import above is found only if there is a file `getUser.js`. It won't be found if file is named `getuser.js`, even if the filesystem is case insensitive.
A specifier is what is written after the from keyword in an import statement.
This ensure two things:
- Project is compatible with Windows or other operating system where filesystem is case sensitive.
- import paths are consistent with what is actually on the filesystem
Case sensitivity can be disabled using [caseSensitive parameter](#Configuration)
## Node module resolution
This resolver consider files are written for browsers by default: Node core modules will be considered as not found.
```js
import value from "specifier"
import { readFile } from "fs"
```
If there is no mapping of `"specifier"` to `"./specifier.js"` the imported file will not be found.
This is because import map consider `"specifier"` as a special kind of specifier called bare specifier.
And every bare specifier must have a mapping or it cannot be resolved.
To fix this either add a mapping or put explicitely `"./specifier.js"`.
The import above would be reported by ESLint as not found.
Please note that `"specifier.js"` is also a bare specifier. You should write `"./specifier.js"` instead.
If the file is written for Node.js, you can consider node core modules as found and enable node module resolution using [node parameter](#Configuration)
# Extensionless import
## Extensionless import

@@ -150,5 +168,27 @@ Extensionless import means an import where the specifier omits the file extension.

But if for some reason this is problematic you can still configure `@jsenv/importmap-eslint-resolver` to understand these extensionless specifiers.
But if for some reason this is problematic you can allow extensionless specifiers using [defaultExtension parameter](#Configuration)
## Bare specifier
A specifier is what is written after the from keyword in an import statement.
```js
import value from "specifier"
```
If there is no mapping of `"specifier"` to `"./specifier.js"` the imported file will not be found.
This is because import map consider `"specifier"` as a special kind of specifier called bare specifier.
And every bare specifier must have a mapping or it cannot be resolved.
To fix this either add a mapping or put explicitely `"./specifier.js"`.
Please note that `"specifier.js"` is also a bare specifier. You should write `"./specifier.js"` instead.
# Configuration
<details>
<summary>importMapFileRelativeUrl parameter</summary>
`importMapFileRelativeUrl` parameter is a string leading to an importmap file. This parameter is optional and `undefined` by default.
```js
module.exports = {

@@ -160,3 +200,3 @@ plugins: ["import"],

projectDirectoryUrl: __dirname,
defaultExtension: true,
importMapFileRelativeUrl: "./project.importmap",
},

@@ -168,9 +208,112 @@ },

By passing `defaultExtension: true` you tell `@jsenv/importmap-eslint-resolver` to automatically add the importer extension when its omitted.
</details>
<details>
<summary>caseSensitive parameter</summary>
`caseSensitive` parameter is a boolean indicating if the file path will be case sensitive. This parameter is optional and enabled by default. See [Case sensitivity](#Case-sensitivity).
```js
module.exports = {
plugins: ["import"],
settings: {
"import/resolver": {
[require.resolve("@jsenv/importmap-eslint-resolver")]: {
projectDirectoryUrl: __dirname,
importMapFileRelativeUrl: "./project.importmap",
caseSensitive: false,
},
},
},
}
```
</details>
<details>
<summary>defaultExtension parameter</summary>
`defaultExtension` parameter is a boolean indicating if a default extension will be automatically added to import without file extension. This parameter is optional and disabled by default. See [Extensionless import](#Extensionless-import)
When enabled the following import
```js
import { value } from "./file"
```
If written in `index.js`, searches file at `file.js`.<br />
If written in `index.ts`, searches file at `file.ts`.
Will search for a file with an extension. The extension is "inherited" from the file where the import is written:
If written in `whatever.js`, searches at `file.js`.<br />
If written in `whatever.ts`, searches at `file.ts`.
</details>
<details>
<summary>node parameter</summary>
`node` parameter is a boolean indicating if the file are written for Node.js. This parameter is optional and disabled by default. See [Node core modules](#Node-core-modules)
When enabled node core modules (path, fs, url, etc) will be considered as found.
```js
module.exports = {
plugins: ["import"],
settings: {
"import/resolver": {
[require.resolve("@jsenv/importmap-eslint-resolver")]: {
projectDirectoryUrl: __dirname,
importMapFileRelativeUrl: "./project.importmap",
node: true,
},
},
},
}
```
</details>
# Advanced configuration example
In a project mixing files written for the browser AND for Node.js you should tell ESLint which are which. This is possible thanks to `overrides`. `overrides` is documented on ESLint in [Configuration Based on Glob Patterns](https://eslint.org/docs/user-guide/configuring/configuration-files#configuration-based-on-glob-patterns).
`.eslintrc.cjs`
```js
const importResolverPath = require.resolve("@jsenv/importmap-eslint-resolver")
module.exports = {
plugins: ["import"],
env: {
es6: true,
// ESLint will consider all files as written for a browser by default
browser: true,
node: false,
},
settings: {
"import/resolver": {
[importResolverPath]: {
projectDirectoryUrl: __dirname,
importMapFileRelativeUrl: "./project.importmap",
},
},
},
overrides: [
{
// ESLint will consider all files inside script/ and ending with .cjs as written for Node.js
files: ["script/**/*.js", "**/*.cjs"],
env: {
es6: true,
browser: false,
node: true,
},
settings: {
"import/resolver": {
[importResolverPath]: {
node: true,
},
},
},
},
],
}
```

@@ -5,9 +5,7 @@ // https://github.com/benmosher/eslint-plugin-import/blob/master/resolvers/node/index.js

import { readFileSync, statSync, realpathSync } from "fs"
import { statSync, realpathSync } from "fs"
import { createLogger } from "@jsenv/logger"
import { normalizeImportMap, resolveImport } from "@jsenv/import-map"
import { isSpecifierForNodeCoreModule } from "@jsenv/import-map/src/isSpecifierForNodeCoreModule.js"
import {
assertAndNormalizeDirectoryUrl,
resolveUrl,
ensureWindowsDriveLetter,

@@ -18,8 +16,5 @@ urlIsInsideOf,

} from "@jsenv/util"
import { applyImportMapResolution } from "./internal/resolution-import-map.js"
import { applyNodeModuleResolution } from "./internal/resolution-node.js"
const applyUrlResolution = (specifier, importer) => {
const url = resolveUrl(specifier, importer)
return ensureWindowsDriveLetter(url, importer)
}
export const interfaceVersion = 2

@@ -33,3 +28,3 @@

projectDirectoryUrl,
importMapFileRelativeUrl = "./import-map.importmap",
importMapFileRelativeUrl,
caseSensitive = true,

@@ -39,2 +34,3 @@ ignoreOutside = false,

node = false,
disableNodeModuleResolution = false,
},

@@ -44,35 +40,2 @@ ) => {

let importMap
if (typeof importMapFileRelativeUrl === "undefined") {
importMap = undefined
} else if (typeof importMapFileRelativeUrl === "string") {
const importMapFileUrl = applyUrlResolution(importMapFileRelativeUrl, projectDirectoryUrl)
if (ignoreOutside && !urlIsInsideOf(importMapFileUrl, projectDirectoryUrl)) {
logger.warn(`import map file is outside project.
--- import map file ---
${urlToFileSystemPath(importMapFileUrl)}
--- project directory ---
${urlToFileSystemPath(projectDirectoryUrl)}`)
}
try {
const importMapFilePath = urlToFileSystemPath(importMapFileUrl)
const importMapFileBuffer = readFileSync(importMapFilePath)
const importMapFileString = String(importMapFileBuffer)
importMap = JSON.parse(importMapFileString)
importMap = normalizeImportMap(importMap, projectDirectoryUrl)
} catch (e) {
if (e && e.code === "ENOENT") {
importMap = {}
} else {
throw e
}
}
} else {
throw new TypeError(
`importMapFileRelativeUrl must be a string, got ${importMapFileRelativeUrl}`,
)
}
const logger = createLogger({ logLevel })

@@ -101,29 +64,88 @@

try {
let importUrl
try {
importUrl = resolveImport({
specifier,
importer,
importMap,
defaultExtension,
})
} catch (e) {
if (e.message.includes("bare specifier")) {
// this is an expected error and the file cannot be found
logger.debug("unmapped bare specifier")
return {
found: false,
path: null,
}
let importUrl = applyImportResolution(specifier, {
logger,
projectDirectoryUrl,
importMapFileRelativeUrl,
importer,
defaultExtension,
node,
disableNodeModuleResolution,
})
if (!importUrl) {
return {
found: false,
path: null,
}
// this is an unexpected error
throw e
}
importUrl = ensureWindowsDriveLetter(importUrl, importer)
if (importUrl.startsWith("file://")) {
const importFilePath = urlToFileSystemPath(importUrl)
return handleFileUrl(importUrl, {
logger,
projectDirectoryUrl,
ignoreOutside,
caseSensitive,
})
}
if (ignoreOutside && !urlIsInsideOf(importUrl, projectDirectoryUrl)) {
logger.warn(`ignoring import outside project
if (importUrl.startsWith("https://") || importUrl.startsWith("http://")) {
logger.debug(`-> consider found because of http(s) scheme ${importUrl}`)
return handleHttpUrl(importUrl)
}
logger.debug(`-> consider not found because of scheme ${importUrl}`)
return handleRemainingUrl(importUrl)
} catch (e) {
logger.error(e.stack)
return {
found: false,
path: null,
}
}
}
const applyImportResolution = (
specifier,
{
logger,
projectDirectoryUrl,
importMapFileRelativeUrl,
importer,
defaultExtension,
node,
disableNodeModuleResolution,
},
) => {
const importResolutionResult = applyImportMapResolution(specifier, {
importer,
logger,
projectDirectoryUrl,
importMapFileRelativeUrl,
defaultExtension,
})
if (importResolutionResult) {
return importResolutionResult
}
if (node && !disableNodeModuleResolution) {
const nodeModuleResolutionResult = applyNodeModuleResolution(specifier, {
importer,
logger,
})
return nodeModuleResolutionResult
}
return null
}
const handleFileUrl = (
importUrl,
{ logger, projectDirectoryUrl, ignoreOutside, caseSensitive },
) => {
const importFilePath = urlToFileSystemPath(importUrl)
if (ignoreOutside && !urlIsInsideOf(importUrl, projectDirectoryUrl)) {
logger.warn(`ignoring import outside project
--- import file ---

@@ -134,33 +156,25 @@ ${importFilePath}

`)
return {
found: false,
path: importFilePath,
}
}
return {
found: false,
path: importFilePath,
}
}
if (pathLeadsToFile(importFilePath)) {
if (caseSensitive) {
const importFileRealPath = realpathSync.native(importFilePath)
if (importFileRealPath !== importFilePath) {
logger.warn(
`WARNING: file found at ${importFilePath} but would not be found on a case sensitive filesystem.
if (!pathLeadsToFile(importFilePath)) {
logger.debug(`-> file not found at ${importUrl}`)
return {
found: false,
path: importFilePath,
}
}
if (caseSensitive) {
const importFileRealPath = realpathSync.native(importFilePath)
if (importFileRealPath !== importFilePath) {
logger.warn(
`WARNING: file found at ${importFilePath} but would not be found on a case sensitive filesystem.
The real file path is ${importFileRealPath}.
You can choose to disable this warning by disabling case sensitivity.
If you do so keep in mind windows users would not find that file.`,
)
return {
found: false,
path: importFilePath,
}
}
}
logger.debug(`-> found file at ${importUrl}`)
return {
found: true,
path: importFilePath,
}
}
logger.debug(`-> file not found at ${importUrl}`)
)
return {

@@ -171,25 +185,24 @@ found: false,

}
}
if (importUrl.startsWith("https://") || importUrl.startsWith("http://")) {
// this api is synchronous we cannot check
// if a remote http/https file is available
logger.debug(`-> consider found because of http(s) scheme ${importUrl}`)
logger.debug(`-> found file at ${importUrl}`)
return {
found: true,
path: importFilePath,
}
}
return {
found: true,
path: null,
}
}
const handleHttpUrl = () => {
// this api is synchronous we cannot check
// if a remote http/https file is available
return {
found: true,
path: null,
}
}
logger.debug(`-> consider not found because of scheme ${importUrl}`)
return {
found: false,
path: null,
}
} catch (e) {
logger.error(e.stack)
return {
found: false,
path: null,
}
const handleRemainingUrl = () => {
return {
found: false,
path: null,
}

@@ -196,0 +209,0 @@ }

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc