diff --git a/.changeset/eleven-cobras-care.md b/.changeset/eleven-cobras-care.md
new file mode 100644
index 00000000..3d4fa8aa
--- /dev/null
+++ b/.changeset/eleven-cobras-care.md
@@ -0,0 +1,6 @@
+---
+"eslint-mdx": minor
+"eslint-plugin-mdx": minor
+---
+
+refactor: remove unnecessary `loadEsmModule` helper, use native `import()` instead
diff --git a/README.md b/README.md
index 68637b97..8208e93c 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-[](https://github.com/mdx-js/eslint-mdx/actions/workflows/ci.yml)
+[](https://github.com/mdx-js/eslint-mdx/actions/workflows/ci.yml?query=branch%3Amaster)
[](https://codecov.io/gh/mdx-js/eslint-mdx)
[](https://github.com/plantain-00/type-coverage)
[](https://github.com/mdx-js/eslint-mdx/releases)
diff --git a/packages/eslint-mdx/README.md b/packages/eslint-mdx/README.md
index a9d2b573..8208e93c 100644
--- a/packages/eslint-mdx/README.md
+++ b/packages/eslint-mdx/README.md
@@ -10,7 +10,7 @@
-[](https://github.com/mdx-js/eslint-mdx/actions/workflows/ci.yml)
+[](https://github.com/mdx-js/eslint-mdx/actions/workflows/ci.yml?query=branch%3Amaster)
[](https://codecov.io/gh/mdx-js/eslint-mdx)
[](https://github.com/plantain-00/type-coverage)
[](https://github.com/mdx-js/eslint-mdx/releases)
@@ -235,21 +235,21 @@ If you're using [remark-lint][] feature with [Prettier][] both together, you can
}
```
+[](https://github.com/sponsors/JounQin)
+
## Sponsors
-| 1stG | RxTS | UnTS |
-| ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
-| [](https://opencollective.com/1stG) | [](https://opencollective.com/rxts) | [](https://opencollective.com/unts) |
+| 1stG | RxTS | UnTS |
+| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| [](https://opencollective.com/1stG) | [](https://opencollective.com/rxts) | [](https://opencollective.com/unts) |
[](https://opencollective.com/unified)
## Backers
-[](https://github.com/sponsors/JounQin)
-
-| 1stG | RxTS | UnTS |
-| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
-| [](https://opencollective.com/1stG) | [](https://opencollective.com/rxts) | [](https://opencollective.com/unts) |
+| 1stG | RxTS | UnTS |
+| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
+| [](https://opencollective.com/1stG) | [](https://opencollective.com/rxts) | [](https://opencollective.com/unts) |
[](https://opencollective.com/unified)
diff --git a/packages/eslint-mdx/src/helpers.ts b/packages/eslint-mdx/src/helpers.ts
index 47f143a1..fdbdd995 100644
--- a/packages/eslint-mdx/src/helpers.ts
+++ b/packages/eslint-mdx/src/helpers.ts
@@ -7,10 +7,6 @@ import type { Point } from 'unist'
import type { CjsRequire, NormalPosition } from './types.js'
-export const last = (items: T[] | readonly T[]) =>
- // eslint-disable-next-line unicorn/prefer-at -- FIXME: Node 16.6+ required
- items && items[items.length - 1]
-
export const arrayify = ? S : T>(
...args: T[]
) =>
@@ -45,30 +41,9 @@ export const getPhysicalFilename = (
return filename
}
-/**
- * ! copied from https://github.com/just-jeb/angular-builders/blob/master/packages/custom-webpack/src/utils.ts#L53-L67
- *
- * This uses a dynamic import to load a module which may be ESM.
- * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
- * will currently, unconditionally downlevel dynamic import into a require call.
- * require calls cannot load ESM code and will result in a runtime error. To workaround
- * this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
- * Once TypeScript provides support for keeping the dynamic import this workaround can
- * be dropped.
- *
- * @param modulePath The path of the module to load.
- * @returns A Promise that resolves to the dynamically imported module.
- */
-/* istanbul ignore next */
-export const loadEsmModule = (modulePath: URL | string): Promise =>
- // eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-call
- new Function('modulePath', `return import(modulePath);`)(
- modulePath,
- ) as Promise
-
/* istanbul ignore next -- used in worker */
-export const getPositionAtFactory = (text: string) => {
- const lines = text.split('\n')
+export const getPositionAtFactory = (code: string) => {
+ const lines = code.split('\n')
return (offset: number): Position => {
let currOffset = 0
@@ -91,19 +66,19 @@ export const getPositionAtFactory = (text: string) => {
export const normalizePosition = ({
start,
end,
- text,
+ code,
}: {
start: Point | { offset: number }
end: Point | { offset: number }
- text?: string
+ code?: string
}): NormalPosition => {
const startOffset = start.offset
const endOffset = end.offset
const range: [number, number] = [startOffset, endOffset]
const getPositionAt =
- text == null
+ code == null
? null
- : /* istanbul ignore next -- used in worker */ getPositionAtFactory(text)
+ : /* istanbul ignore next -- used in worker */ getPositionAtFactory(code)
return {
start: startOffset,
end: endOffset,
@@ -123,10 +98,10 @@ export const normalizePosition = ({
/* istanbul ignore next -- used in worker */
export const prevCharOffsetFactory =
- (text: string) =>
+ (code: string) =>
(offset: number): number => {
for (let i = offset; i >= 0; i--) {
- const char = text[i]
+ const char = code[i]
if (/^\S$/.test(char)) {
return i
}
diff --git a/packages/eslint-mdx/src/parser.ts b/packages/eslint-mdx/src/parser.ts
index 79d0c927..dd19eb7a 100644
--- a/packages/eslint-mdx/src/parser.ts
+++ b/packages/eslint-mdx/src/parser.ts
@@ -47,17 +47,12 @@ export class Parser {
)
}
- const physicalFilename = getPhysicalFilename(filePath)
-
let result: WorkerParseResult
try {
result = performSyncWork({
- fileOptions: {
- path: physicalFilename,
- value: code,
- },
- physicalFilename,
+ filePath: getPhysicalFilename(filePath),
+ code,
isMdx,
ignoreRemarkConfig,
})
diff --git a/packages/eslint-mdx/src/types.ts b/packages/eslint-mdx/src/types.ts
index 421632a9..36213bcb 100644
--- a/packages/eslint-mdx/src/types.ts
+++ b/packages/eslint-mdx/src/types.ts
@@ -3,7 +3,6 @@ import type { AST, Linter } from 'eslint'
import type { BaseNode, Program } from 'estree'
import type { JSXElement } from 'estree-jsx'
import type { Root } from 'mdast'
-import type { VFileOptions } from 'vfile'
import type { VFileMessage } from 'vfile-message'
export interface ParserOptions extends Linter.ParserOptions {
@@ -24,8 +23,9 @@ export interface NormalPosition {
}
export interface WorkerOptions {
- fileOptions: VFileOptions
- physicalFilename: string
+ filePath: string
+ code: string
+ cwd?: string
isMdx: boolean
process?: boolean
ignoreRemarkConfig?: boolean
@@ -40,7 +40,7 @@ export interface WorkerParseResult {
export interface WorkerProcessResult {
messages: VFileMessage[]
- content: string
+ content?: string
}
export type WorkerResult = WorkerParseResult | WorkerProcessResult
diff --git a/packages/eslint-mdx/src/worker.ts b/packages/eslint-mdx/src/worker.ts
index cd80ff8b..67a4914d 100644
--- a/packages/eslint-mdx/src/worker.ts
+++ b/packages/eslint-mdx/src/worker.ts
@@ -2,6 +2,7 @@
import path from 'node:path'
import { pathToFileURL } from 'node:url'
+import { promisify } from 'node:util'
import type { Parser, Token, TokenType, tokTypes as _tokTypes } from 'acorn'
import * as acorn from 'acorn'
@@ -30,7 +31,6 @@ import type {
import { visit as visitEstree } from 'estree-util-visit'
import type { Nodes, Root } from 'mdast'
import type { Options } from 'micromark-extension-mdx-expression'
-import type * as remarkLintFileExtension from 'remark-lint-file-extension'
import remarkMdx from 'remark-mdx'
import remarkParse from 'remark-parse'
import remarkStringify from 'remark-stringify'
@@ -41,12 +41,11 @@ import { Configuration } from 'unified-engine'
import type { Node } from 'unist'
import { visit } from 'unist-util-visit'
import { ok as assert } from 'uvu/assert'
-import { VFile } from 'vfile'
+import { VFile, type VFileOptions } from 'vfile'
import type { VFileMessage } from 'vfile-message'
import {
cjsRequire,
- loadEsmModule,
nextCharOffsetFactory,
normalizePosition,
prevCharOffsetFactory,
@@ -61,8 +60,6 @@ import type {
WorkerResult,
} from './types.ts'
-let config: Configuration
-
let acornParser: typeof Parser
let tokTypes: typeof _tokTypes
@@ -76,26 +73,21 @@ export const processorCache = new Map<
Processor
>()
-const getRemarkConfig = async (searchFrom: string) => {
- if (!config) {
- config = new Configuration({
+let configLoad: (filePath: string) => Promise
+
+const getRemarkConfig = async (filePath: string) => {
+ if (!configLoad) {
+ const config = new Configuration({
cwd: process.cwd(),
packageField: 'remarkConfig',
pluginPrefix: 'remark',
rcName: '.remarkrc',
detectConfig: true,
})
+ configLoad = promisify(config.load.bind(config))
}
- return new Promise((resolve, reject) =>
- config.load(searchFrom, (error, result) => {
- if (error) {
- reject(error)
- } else {
- resolve(result)
- }
- }),
- )
+ return configLoad(filePath)
}
const getRemarkMdxOptions = (tokens: Token[]): Options => ({
@@ -112,11 +104,11 @@ const getRemarkMdxOptions = (tokens: Token[]): Options => ({
const sharedTokens: Token[] = []
export const getRemarkProcessor = async (
- searchFrom: string,
+ filePath: string,
isMdx: boolean,
ignoreRemarkConfig?: boolean,
) => {
- const initCacheKey = `${String(isMdx)}-${searchFrom}`
+ const initCacheKey = `${String(isMdx)}-${filePath}`
let cachedProcessor = processorCache.get(initCacheKey)
@@ -124,7 +116,7 @@ export const getRemarkProcessor = async (
return cachedProcessor
}
- const result = ignoreRemarkConfig ? null : await getRemarkConfig(searchFrom)
+ const result = ignoreRemarkConfig ? null : await getRemarkConfig(filePath)
const cacheKey = result?.filePath
? `${String(isMdx)}-${result.filePath}`
@@ -148,11 +140,7 @@ export const getRemarkProcessor = async (
if (plugins.length > 0) {
try {
plugins.push([
- (
- await loadEsmModule(
- 'remark-lint-file-extension',
- )
- ).default,
+ (await import('remark-lint-file-extension')).default,
false,
])
} catch {
@@ -196,8 +184,9 @@ function isExpressionStatement(
runAsWorker(
async ({
- fileOptions,
- physicalFilename,
+ filePath,
+ code,
+ cwd,
isMdx,
process,
ignoreRemarkConfig,
@@ -215,11 +204,17 @@ runAsWorker(
}
const processor = await getRemarkProcessor(
- physicalFilename,
+ filePath,
isMdx,
ignoreRemarkConfig,
)
+ const fileOptions: VFileOptions = {
+ path: filePath,
+ value: code,
+ cwd,
+ }
+
if (process) {
const file = new VFile(fileOptions)
try {
@@ -252,14 +247,14 @@ runAsWorker(
if (!TokenTranslator) {
TokenTranslator = (
- await loadEsmModule(
+ (await import(
pathToFileURL(
path.resolve(
cjsRequire.resolve('espree/package.json'),
'../lib/token-translator.js',
),
- ),
- )
+ ).href
+ )) as typeof TokenTranslator_
).default
}
@@ -270,8 +265,7 @@ runAsWorker(
}
}
- const text = fileOptions.value as string
- const tokenTranslator = new TokenTranslator(tt, text)
+ const tokenTranslator = new TokenTranslator(tt, code)
const root = processor.parse(fileOptions)
@@ -283,16 +277,16 @@ runAsWorker(
// TODO: merge with `tokens.ts`
if (isMdx) {
- const prevCharOffset = prevCharOffsetFactory(text)
- const nextCharOffset = nextCharOffsetFactory(text)
+ const prevCharOffset = prevCharOffsetFactory(code)
+ const nextCharOffset = nextCharOffsetFactory(code)
const normalizeNode = (start: number, end: number) => ({
...normalizePosition({
start: { offset: start },
end: { offset: end },
- text,
+ code,
}),
- raw: text.slice(start, end),
+ raw: code.slice(start, end),
})
const handleJsxName = (
@@ -478,7 +472,7 @@ runAsWorker(
const nodeNameLength = node.name.length
const nodeNameStart = nextCharOffset(nodeStart + 1)
- const selfClosing = text[lastCharOffset] === '/'
+ const selfClosing = code[lastCharOffset] === '/'
let lastAttrOffset = nodeNameStart + nodeNameLength - 1
@@ -488,11 +482,11 @@ runAsWorker(
const prevOffset = prevCharOffset(lastCharOffset)
const slashOffset = prevCharOffset(prevOffset - nodeNameLength)
assert(
- text[slashOffset] === '/',
- `expect \`${text[slashOffset]}\` to be \`/\`, the node is ${node.name}`,
+ code[slashOffset] === '/',
+ `expect \`${code[slashOffset]}\` to be \`/\`, the node is ${node.name}`,
)
const tagStartOffset = prevCharOffset(slashOffset - 1)
- assert(text[tagStartOffset] === '<')
+ assert(code[tagStartOffset] === '<')
closingElement = {
...normalizeNode(tagStartOffset, nodeEnd),
type: 'JSXClosingElement',
@@ -517,8 +511,8 @@ runAsWorker(
attrValStart = prevCharOffset(attrValStart - 1)
attrValEnd = nextCharOffset(attrValEnd)
- assert(text[attrValStart] === '{')
- assert(text[attrValEnd] === '}')
+ assert(code[attrValStart] === '{')
+ assert(code[attrValEnd] === '}')
lastAttrOffset = attrValEnd
@@ -566,14 +560,14 @@ runAsWorker(
attrStart + attrNameLength,
)
- assert(text[attrEqualOffset] === '=')
+ assert(code[attrEqualOffset] === '=')
let attrValuePos: NormalPosition
if (typeof attrValue === 'string') {
const attrQuoteOffset = nextCharOffset(attrEqualOffset + 1)
- const attrQuote = text[attrQuoteOffset]
+ const attrQuote = code[attrQuoteOffset]
assert(attrQuote === '"' || attrQuote === "'")
@@ -581,7 +575,7 @@ runAsWorker(
attrQuoteOffset + attrValue.length + 1,
)
- assert(text[lastAttrOffset] === attrQuote)
+ assert(code[lastAttrOffset] === attrQuote)
attrValuePos = normalizeNode(
attrQuoteOffset,
@@ -595,8 +589,8 @@ runAsWorker(
attrValStart = prevCharOffset(attrValStart - 1)
attrValEnd = nextCharOffset(attrValEnd)
- assert(text[attrValStart] === '{')
- assert(text[attrValEnd] === '}')
+ assert(code[attrValStart] === '{')
+ assert(code[attrValEnd] === '}')
lastAttrOffset = attrValEnd
@@ -635,13 +629,13 @@ runAsWorker(
}
let nextOffset = nextCharOffset(lastAttrOffset + 1)
- let nextChar = text[nextOffset]
+ let nextChar = code[nextOffset]
const expectedNextChar = selfClosing ? '/' : '>'
if (nextChar !== expectedNextChar) {
nextOffset = /** @type {number} */ nextCharOffset(lastAttrOffset)
- nextChar = text[nextOffset]
+ nextChar = code[nextOffset]
}
assert(
@@ -740,7 +734,7 @@ runAsWorker(
},
)
- for (const token of restoreTokens(text, root, sharedTokens, tt, visit)) {
+ for (const token of restoreTokens(code, root, sharedTokens, tt, visit)) {
tokenTranslator.onToken(token, {
ecmaVersion: 'latest',
tokens: tokens as TokenTranslator_.EsprimaToken[],
diff --git a/packages/eslint-plugin-mdx/README.md b/packages/eslint-plugin-mdx/README.md
index a9d2b573..8208e93c 100644
--- a/packages/eslint-plugin-mdx/README.md
+++ b/packages/eslint-plugin-mdx/README.md
@@ -10,7 +10,7 @@
-[](https://github.com/mdx-js/eslint-mdx/actions/workflows/ci.yml)
+[](https://github.com/mdx-js/eslint-mdx/actions/workflows/ci.yml?query=branch%3Amaster)
[](https://codecov.io/gh/mdx-js/eslint-mdx)
[](https://github.com/plantain-00/type-coverage)
[](https://github.com/mdx-js/eslint-mdx/releases)
@@ -235,21 +235,21 @@ If you're using [remark-lint][] feature with [Prettier][] both together, you can
}
```
+[](https://github.com/sponsors/JounQin)
+
## Sponsors
-| 1stG | RxTS | UnTS |
-| ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
-| [](https://opencollective.com/1stG) | [](https://opencollective.com/rxts) | [](https://opencollective.com/unts) |
+| 1stG | RxTS | UnTS |
+| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
+| [](https://opencollective.com/1stG) | [](https://opencollective.com/rxts) | [](https://opencollective.com/unts) |
[](https://opencollective.com/unified)
## Backers
-[](https://github.com/sponsors/JounQin)
-
-| 1stG | RxTS | UnTS |
-| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
-| [](https://opencollective.com/1stG) | [](https://opencollective.com/rxts) | [](https://opencollective.com/unts) |
+| 1stG | RxTS | UnTS |
+| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
+| [](https://opencollective.com/1stG) | [](https://opencollective.com/rxts) | [](https://opencollective.com/unts) |
[](https://opencollective.com/unified)
diff --git a/packages/eslint-plugin-mdx/src/processors/helpers.ts b/packages/eslint-plugin-mdx/src/processors/helpers.ts
index 9fd6468f..5348490a 100644
--- a/packages/eslint-plugin-mdx/src/processors/helpers.ts
+++ b/packages/eslint-plugin-mdx/src/processors/helpers.ts
@@ -1,5 +1,3 @@
-import { last } from 'eslint-mdx'
-
export const DEFAULT_LANGUAGE_MAPPER: Record = {
javascript: 'js',
javascriptreact: 'jsx',
@@ -14,7 +12,7 @@ export function getShortLang(
filename: string,
languageMapper?: Record | false,
): string {
- const language = last(filename.split('.'))
+ const language = filename.split('.').at(-1)
if (languageMapper === false) {
return language
}
diff --git a/packages/eslint-plugin-mdx/src/rules/remark.ts b/packages/eslint-plugin-mdx/src/rules/remark.ts
index 931b3a71..3153fc06 100644
--- a/packages/eslint-plugin-mdx/src/rules/remark.ts
+++ b/packages/eslint-plugin-mdx/src/rules/remark.ts
@@ -46,18 +46,13 @@ export const remark: Rule.RuleModule = {
const ignoreRemarkConfig = Boolean(options.ignoreRemarkConfig)
- const physicalFilename = getPhysicalFilename(filename)
-
const sourceText = sourceCode.getText(node)
const { messages, content: fixedText } = performSyncWork({
- fileOptions: {
- path: physicalFilename,
- value: sourceText,
- // eslint-disable-next-line sonarjs/deprecation -- FIXME: ESLint 8.40+ required
- cwd: context.getCwd(),
- },
- physicalFilename,
+ filePath: getPhysicalFilename(filename),
+ code: sourceText,
+ // eslint-disable-next-line sonarjs/deprecation -- FIXME: ESLint 8.40+ required
+ cwd: context.getCwd(),
isMdx,
process: true,
ignoreRemarkConfig,
@@ -110,7 +105,7 @@ export const remark: Rule.RuleModule = {
: point,
node,
fix:
- fixedText === sourceText
+ fixedText == null || fixedText === sourceText
? null
: () =>
fixed++