Skip to content

Commit 94a08af

Browse files
authored
feat: fallback to async API on processSync error via synckit (#307)
* feat: fallback to async API on processSync error via synckit * refactor: catch process error in worker instead * feat: cache broken sync processor * fix: mark processor broken
1 parent a893aa7 commit 94a08af

File tree

11 files changed

+199
-21
lines changed

11 files changed

+199
-21
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ before_install:
1818
install: yarn --frozen-lockfile
1919

2020
script:
21-
- yarn build
2221
- yarn lint
2322
- yarn test
2423

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"lint:es": "cross-env PARSER_NO_WATCH=true eslint . --cache --ext js,md,ts -f friendly",
1919
"lint:ts": "tslint -p . -t stylish",
2020
"postinstall": "simple-git-hooks && yarn-deduplicate --strategy fewer || exit 0",
21+
"prelint": "yarn build",
2122
"prerelease": "yarn build",
2223
"release": "lerna publish --conventional-commits --create-release github --yes",
2324
"test": "jest",
@@ -35,6 +36,7 @@
3536
"lerna": "^4.0.0",
3637
"npm-run-all": "^4.1.5",
3738
"react": "^17.0.2",
39+
"remark-validate-links": "^10.0.4",
3840
"ts-jest": "^26.5.5",
3941
"ts-node": "^9.1.1",
4042
"tslint": "^6.1.3",

packages/eslint-plugin-mdx/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"remark-mdx": "^1.6.22",
4040
"remark-parse": "^8.0.3",
4141
"remark-stringify": "^8.1.1",
42+
"synckit": "^0.1.5",
4243
"tslib": "^2.2.0",
4344
"unified": "^9.2.1",
4445
"vfile": "^4.2.1"

packages/eslint-plugin-mdx/src/rules/remark.ts

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import path from 'path'
33
import type { Rule } from 'eslint'
44
import type { ParserOptions } from 'eslint-mdx'
55
import { DEFAULT_EXTENSIONS, MARKDOWN_EXTENSIONS } from 'eslint-mdx'
6+
import { createSyncFn } from 'synckit'
7+
import type { FrozenProcessor } from 'unified'
68
import vfile from 'vfile'
79

810
import { getPhysicalFilename, getRemarkProcessor } from './helpers'
9-
import type { RemarkLintMessage } from './types'
11+
import type { ProcessSync, RemarkLintMessage } from './types'
12+
13+
const processSync = createSyncFn(require.resolve('../worker')) as ProcessSync
14+
15+
const brokenCache = new WeakMap<FrozenProcessor, true>()
1016

1117
export const remark: Rule.RuleModule = {
1218
meta: {
@@ -36,22 +42,45 @@ export const remark: Rule.RuleModule = {
3642
if (!isMdx && !isMarkdown) {
3743
return
3844
}
45+
46+
const physicalFilename = getPhysicalFilename(filename)
47+
3948
const sourceText = sourceCode.getText(node)
40-
const remarkProcessor = getRemarkProcessor(
41-
getPhysicalFilename(filename),
42-
isMdx,
43-
)
44-
const file = vfile({
49+
const remarkProcessor = getRemarkProcessor(physicalFilename, isMdx)
50+
51+
const fileOptions = {
4552
path: filename,
4653
contents: sourceText,
47-
})
54+
}
4855

49-
try {
50-
remarkProcessor.processSync(file)
51-
} catch (err) {
52-
/* istanbul ignore next */
53-
if (!file.messages.includes(err)) {
54-
file.message(err).fatal = true
56+
const file = vfile(fileOptions)
57+
58+
let broken = brokenCache.get(remarkProcessor)
59+
60+
if (broken) {
61+
const { messages } = processSync(fileOptions, physicalFilename, isMdx)
62+
file.messages = messages
63+
} else {
64+
try {
65+
remarkProcessor.processSync(file)
66+
} catch (err) {
67+
/* istanbul ignore else */
68+
if (
69+
(err as Error).message ===
70+
'`processSync` finished async. Use `process` instead'
71+
) {
72+
brokenCache.set(remarkProcessor, (broken = true))
73+
const { messages } = processSync(
74+
fileOptions,
75+
physicalFilename,
76+
isMdx,
77+
)
78+
file.messages = messages
79+
} else {
80+
if (!file.messages.includes(err)) {
81+
file.message(err).fatal = true
82+
}
83+
}
5584
}
5685
}
5786

@@ -102,11 +131,14 @@ export const remark: Rule.RuleModule = {
102131
end.offset == null ? start.offset + 1 : end.offset,
103132
]
104133
const partialText = sourceText.slice(...range)
105-
const fixed = remarkProcessor.processSync(partialText).toString()
134+
const fixed = broken
135+
? processSync(partialText, physicalFilename, isMdx)
136+
: remarkProcessor.processSync(partialText).toString()
106137
return fixer.replaceTextRange(
107138
range,
108-
/* istanbul ignore next */
109-
partialText.endsWith('\n') ? fixed : fixed.slice(0, -1), // remove redundant new line
139+
partialText.endsWith('\n')
140+
? /* istanbul ignore next */ fixed
141+
: fixed.slice(0, -1), // remove redundant new line
110142
)
111143
},
112144
})

packages/eslint-plugin-mdx/src/rules/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Linter } from 'eslint'
22
import type { ExpressionStatement, Node } from 'estree'
33
import type { Attacher } from 'unified'
4+
import type { VFile, VFileOptions } from 'vfile'
45

56
export interface WithParent {
67
parent: NodeWithParent
@@ -25,3 +26,16 @@ export interface RemarkLintMessage {
2526
ruleId: string
2627
severity: Linter.Severity
2728
}
29+
30+
export interface ProcessSync {
31+
(text: string, physicalFilename: string, isFile: boolean): string
32+
(fileOptions: VFileOptions, physicalFilename: string, isFile: boolean): Pick<
33+
VFile,
34+
'messages'
35+
>
36+
(
37+
textOrFileOptions: string | VFileOptions,
38+
physicalFilename: string,
39+
isFile: boolean,
40+
): string | VFileOptions
41+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { runAsWorker } from 'synckit'
2+
import type { VFileOptions } from 'vfile'
3+
import vfile from 'vfile'
4+
5+
import { getRemarkProcessor } from './rules'
6+
7+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
8+
runAsWorker(
9+
async (
10+
textOrFileOptions: string | VFileOptions,
11+
physicalFilename: string,
12+
isMdx: boolean,
13+
) => {
14+
const remarkProcessor = getRemarkProcessor(physicalFilename, isMdx)
15+
const file = vfile(textOrFileOptions)
16+
try {
17+
await remarkProcessor.process(file)
18+
} catch (err) {
19+
if (!file.messages.includes(err)) {
20+
file.message(err).fatal = true
21+
}
22+
}
23+
return typeof textOrFileOptions === 'string'
24+
? file.toString()
25+
: { messages: file.messages }
26+
},
27+
)

test/fixtures/async/.remarkrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"plugins": [
3+
[
4+
"validate-links",
5+
2
6+
]
7+
]
8+
}

test/remark.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,16 @@ ruleTester.run('remark 1', remark, {
4141
parserOptions,
4242
// dark hack
4343
get filename() {
44-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
4544
processorCache.clear()
4645
return path.resolve(userDir, '../test.md')
4746
},
4847
},
48+
{
49+
code: '<header>Header6</header>',
50+
parser,
51+
parserOptions,
52+
filename: path.resolve(__dirname, 'fixtures/async/test.mdx'),
53+
},
4954
],
5055
invalid: [
5156
{
@@ -68,5 +73,25 @@ ruleTester.run('remark 1', remark, {
6873
},
6974
],
7075
},
76+
{
77+
code: '[CHANGELOG](./CHANGELOG.md)',
78+
parser,
79+
parserOptions,
80+
filename: path.resolve(__dirname, 'fixtures/async/test.mdx'),
81+
errors: [
82+
{
83+
message: JSON.stringify({
84+
reason: 'Link to unknown file: `CHANGELOG.md`',
85+
source: 'remark-validate-links',
86+
ruleId: 'missing-file',
87+
severity: 1,
88+
}),
89+
line: 1,
90+
column: 1,
91+
endLine: 1,
92+
endColumn: 28,
93+
},
94+
],
95+
},
7196
],
7297
})

tsconfig.eslint.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "./tsconfig.base",
3+
"compilerOptions": {
4+
"baseUrl": ".",
5+
"paths": {
6+
"eslint-mdx": ["packages/eslint-mdx/src"],
7+
"eslint-plugin-mdx": ["packages/eslint-plugin-mdx/src"]
8+
}
9+
}
10+
}

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": "./tsconfig.base.json",
2+
"extends": "./tsconfig.base",
33
"compilerOptions": {
44
"noEmit": true
55
},

0 commit comments

Comments
 (0)