From 67fd7015e3ff58292216ef0092b607b3224d02ab Mon Sep 17 00:00:00 2001 From: JounQin Date: Tue, 27 Apr 2021 01:04:39 +0800 Subject: [PATCH 1/4] feat: fallback to async API on processSync error via synckit --- .travis.yml | 1 - package.json | 2 + packages/eslint-plugin-mdx/package.json | 1 + .../eslint-plugin-mdx/src/rules/helpers.ts | 7 +- .../eslint-plugin-mdx/src/rules/remark.ts | 58 ++++++++++++++--- packages/eslint-plugin-mdx/src/worker.ts | 27 ++++++++ test/fixtures/async/.remarkrc | 8 +++ test/remark.test.ts | 7 +- tsconfig.eslint.json | 10 +++ tsconfig.json | 2 +- yarn.lock | 64 ++++++++++++++++++- 11 files changed, 170 insertions(+), 17 deletions(-) create mode 100644 packages/eslint-plugin-mdx/src/worker.ts create mode 100644 test/fixtures/async/.remarkrc create mode 100644 tsconfig.eslint.json diff --git a/.travis.yml b/.travis.yml index 336a164c..7bf3a72a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,6 @@ before_install: install: yarn --frozen-lockfile script: - - yarn build - yarn lint - yarn test diff --git a/package.json b/package.json index b4f37db8..5c79224f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "lint:es": "cross-env PARSER_NO_WATCH=true eslint . --cache --ext js,md,ts -f friendly", "lint:ts": "tslint -p . -t stylish", "postinstall": "simple-git-hooks && yarn-deduplicate --strategy fewer || exit 0", + "prelint": "yarn build", "prerelease": "yarn build", "release": "lerna publish --conventional-commits --create-release github --yes", "test": "jest", @@ -35,6 +36,7 @@ "lerna": "^4.0.0", "npm-run-all": "^4.1.5", "react": "^17.0.2", + "remark-validate-links": "^10.0.4", "ts-jest": "^26.5.5", "ts-node": "^9.1.1", "tslint": "^6.1.3", diff --git a/packages/eslint-plugin-mdx/package.json b/packages/eslint-plugin-mdx/package.json index 3633c955..5898fb94 100644 --- a/packages/eslint-plugin-mdx/package.json +++ b/packages/eslint-plugin-mdx/package.json @@ -39,6 +39,7 @@ "remark-mdx": "^1.6.22", "remark-parse": "^8.0.3", "remark-stringify": "^8.1.1", + "synckit": "^0.1.5", "tslib": "^2.2.0", "unified": "^9.2.1", "vfile": "^4.2.1" diff --git a/packages/eslint-plugin-mdx/src/rules/helpers.ts b/packages/eslint-plugin-mdx/src/rules/helpers.ts index 83a14b6c..ca1daab5 100644 --- a/packages/eslint-plugin-mdx/src/rules/helpers.ts +++ b/packages/eslint-plugin-mdx/src/rules/helpers.ts @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import { cosmiconfigSync } from 'cosmiconfig' -import { arrayify } from 'eslint-mdx' +import { arrayify, hasProperties } from 'eslint-mdx' import remarkMdx from 'remark-mdx' import remarkParse from 'remark-parse' import remarkStringify from 'remark-stringify' @@ -52,7 +52,10 @@ export const getPhysicalFilename = (filename: string): string => { } } catch (err) { // https://github.com/eslint/eslint/issues/11989 - if ((err as { code: string }).code === 'ENOTDIR') { + if ( + hasProperties<{ code: string }>(err, ['code']) && + err.code === 'ENOTDIR' + ) { return getPhysicalFilename(path.dirname(filename)) } } diff --git a/packages/eslint-plugin-mdx/src/rules/remark.ts b/packages/eslint-plugin-mdx/src/rules/remark.ts index 9eb58d56..30770eba 100644 --- a/packages/eslint-plugin-mdx/src/rules/remark.ts +++ b/packages/eslint-plugin-mdx/src/rules/remark.ts @@ -2,12 +2,29 @@ import path from 'path' import type { Rule } from 'eslint' import type { ParserOptions } from 'eslint-mdx' -import { DEFAULT_EXTENSIONS, MARKDOWN_EXTENSIONS } from 'eslint-mdx' +import { + DEFAULT_EXTENSIONS, + MARKDOWN_EXTENSIONS, + hasProperties, +} from 'eslint-mdx' +import { createSyncFn } from 'synckit' +import type { VFile, VFileOptions } from 'vfile' import vfile from 'vfile' import { getPhysicalFilename, getRemarkProcessor } from './helpers' import type { RemarkLintMessage } from './types' +const processSync = createSyncFn(require.resolve('../worker')) as ( + options: VFileOptions, + physicalFilename: string, + isFile: boolean, +) => { + messages?: VFile['messages'] + error?: { + message: string + } +} + export const remark: Rule.RuleModule = { meta: { type: 'layout', @@ -36,22 +53,43 @@ export const remark: Rule.RuleModule = { if (!isMdx && !isMarkdown) { return } + + const physicalFilename = getPhysicalFilename(filename) + const sourceText = sourceCode.getText(node) - const remarkProcessor = getRemarkProcessor( - getPhysicalFilename(filename), - isMdx, - ) - const file = vfile({ + const remarkProcessor = getRemarkProcessor(physicalFilename, isMdx) + + const fileOptions = { path: filename, contents: sourceText, - }) + } + + const file = vfile(fileOptions) try { remarkProcessor.processSync(file) } catch (err) { - /* istanbul ignore next */ - if (!file.messages.includes(err)) { - file.message(err).fatal = true + /* istanbul ignore else */ + if ( + hasProperties(err, ['message']) && + err.message === + '`processSync` finished async. Use `process` instead' + ) { + const { messages, error } = processSync( + fileOptions, + physicalFilename, + isMdx, + ) + /* istanbul ignore if */ + if (error) { + file.message(error.message).fatal = true + } else { + file.messages = messages + } + } else { + if (!file.messages.includes(err)) { + file.message(err).fatal = true + } } } diff --git a/packages/eslint-plugin-mdx/src/worker.ts b/packages/eslint-plugin-mdx/src/worker.ts new file mode 100644 index 00000000..ee598e29 --- /dev/null +++ b/packages/eslint-plugin-mdx/src/worker.ts @@ -0,0 +1,27 @@ +import { hasProperties } from 'eslint-mdx' +import { runAsWorker } from 'synckit' +import type { VFileOptions } from 'vfile' + +import { getRemarkProcessor } from './rules' + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +runAsWorker( + async (options: VFileOptions, physicalFilename: string, isMdx: boolean) => { + const remarkProcessor = getRemarkProcessor(physicalFilename, isMdx) + try { + const vfile = await remarkProcessor.process(options) + return { + messages: vfile.messages, + } + } catch (err) { + return { + error: { + message: hasProperties(err, ['message']) + ? err.message + : (console.error(err), + 'Fetal error without message, please check the output instead'), + }, + } + } + }, +) diff --git a/test/fixtures/async/.remarkrc b/test/fixtures/async/.remarkrc new file mode 100644 index 00000000..d5193298 --- /dev/null +++ b/test/fixtures/async/.remarkrc @@ -0,0 +1,8 @@ +{ + "plugins": [ + [ + "validate-links", + 2 + ] + ] +} diff --git a/test/remark.test.ts b/test/remark.test.ts index 06be0cca..ebeef85b 100644 --- a/test/remark.test.ts +++ b/test/remark.test.ts @@ -41,11 +41,16 @@ ruleTester.run('remark 1', remark, { parserOptions, // dark hack get filename() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access processorCache.clear() return path.resolve(userDir, '../test.md') }, }, + { + code: '
Header5
', + parser, + parserOptions, + filename: path.resolve(__dirname, 'fixtures/async/test.mdx'), + }, ], invalid: [ { diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 00000000..6055613d --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "eslint-mdx": ["packages/eslint-mdx/src"], + "eslint-plugin-mdx": ["packages/eslint-plugin-mdx/src"] + } + } +} diff --git a/tsconfig.json b/tsconfig.json index e931e294..b41dbae9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.base.json", + "extends": "./tsconfig.base", "compilerOptions": { "noEmit": true }, diff --git a/yarn.lock b/yarn.lock index ab02dcf9..ba5e5138 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4847,6 +4847,11 @@ emittery@^0.7.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== +"emoji-regex@>=6.0.0 <=6.1.1": + version "6.1.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" + integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4= + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -5855,6 +5860,13 @@ gitconfiglocal@^1.0.0: dependencies: ini "^1.3.2" +github-slugger@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.3.0.tgz#9bd0a95c5efdfc46005e82a906ef8e2a059124c9" + integrity sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q== + dependencies: + emoji-regex ">=6.0.0 <=6.1.1" + glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@^5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -6054,6 +6066,13 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== +hosted-git-info@^3.0.0: + version "3.0.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" + integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== + dependencies: + lru-cache "^6.0.0" + hosted-git-info@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" @@ -7362,6 +7381,11 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +levenshtein-edit-distance@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/levenshtein-edit-distance/-/levenshtein-edit-distance-1.0.0.tgz#895baf478cce8b5c1a0d27e45d7c1d978a661e49" + integrity sha1-iVuvR4zOi1waDSfkXXwdl4pmHkk= + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -7733,7 +7757,7 @@ mdast-util-heading-style@^1.0.2: resolved "https://registry.yarnpkg.com/mdast-util-heading-style/-/mdast-util-heading-style-1.0.6.tgz#6410418926fd5673d40f519406b35d17da10e3c5" integrity sha512-8ZuuegRqS0KESgjAGW8zTx4tJ3VNIiIaGFNEzFpRSAQBavVc7AvOo9I4g3crcZBfYisHs4seYh0rAVimO6HyOw== -mdast-util-to-string@^1.0.2: +mdast-util-to-string@^1.0.0, mdast-util-to-string@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== @@ -9287,6 +9311,13 @@ prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +propose@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/propose/-/propose-0.0.5.tgz#48a065d9ec7d4c8667f4050b15c4a2d85dbca56b" + integrity sha1-SKBl2ex9TIZn9AULFcSi2F28pWs= + dependencies: + levenshtein-edit-distance "^1.0.0" + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -10343,6 +10374,19 @@ remark-stringify@^8.1.1: unherit "^1.0.4" xtend "^4.0.1" +remark-validate-links@^10.0.4: + version "10.0.4" + resolved "https://registry.yarnpkg.com/remark-validate-links/-/remark-validate-links-10.0.4.tgz#a2711fa794f691c944faf8126767152dcfee0c47" + integrity sha512-oNGRcsoQkL35WoZKLMMBugDwvHfyu0JPA5vSYkEcvR6YBsFKBo4RedpecuokTK1wgD9l01rPxaQ9dPmRQYFhyg== + dependencies: + github-slugger "^1.0.0" + hosted-git-info "^3.0.0" + mdast-util-to-string "^1.0.0" + propose "0.0.5" + to-vfile "^6.0.0" + trough "^1.0.0" + unist-util-visit "^2.0.0" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -11325,6 +11369,14 @@ symbol-tree@^3.2.4: resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +synckit@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.1.5.tgz#f34462b2e3686bba3dbea2ae13b6e01adff2ffb8" + integrity sha512-s9rDbMJAF5SDEwBGH/DvbN/fb5N1Xu1boL4Uv66idbCbtosNX3ikUsFvGhROmPXsvlMYMcT5ksmkU5RSnkFi9Q== + dependencies: + tslib "^2.2.0" + uuid "^8.3.2" + table@^6.0.4: version "6.0.9" resolved "https://registry.yarnpkg.com/table/-/table-6.0.9.tgz#790a12bf1e09b87b30e60419bafd6a1fd85536fb" @@ -11504,6 +11556,14 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +to-vfile@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/to-vfile/-/to-vfile-6.1.0.tgz#5f7a3f65813c2c4e34ee1f7643a5646344627699" + integrity sha512-BxX8EkCxOAZe+D/ToHdDsJcVI4HqQfmw0tCkp31zf3dNP/XWIAjU4CmeuSwsSoOzOTqHPOL0KUzyZqJplkD0Qw== + dependencies: + is-buffer "^2.0.0" + vfile "^4.0.0" + tough-cookie@^2.3.3, tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -12064,7 +12124,7 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.3.0: +uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== From 5204f5ac09f13bff3981a09b0f6be36ba0d9f980 Mon Sep 17 00:00:00 2001 From: JounQin Date: Tue, 27 Apr 2021 11:02:49 +0800 Subject: [PATCH 2/4] refactor: catch process error in worker instead --- .../eslint-plugin-mdx/src/rules/remark.ts | 16 +++--------- packages/eslint-plugin-mdx/src/worker.ts | 26 +++++++++---------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/packages/eslint-plugin-mdx/src/rules/remark.ts b/packages/eslint-plugin-mdx/src/rules/remark.ts index 30770eba..b476f47c 100644 --- a/packages/eslint-plugin-mdx/src/rules/remark.ts +++ b/packages/eslint-plugin-mdx/src/rules/remark.ts @@ -18,12 +18,7 @@ const processSync = createSyncFn(require.resolve('../worker')) as ( options: VFileOptions, physicalFilename: string, isFile: boolean, -) => { - messages?: VFile['messages'] - error?: { - message: string - } -} +) => Pick export const remark: Rule.RuleModule = { meta: { @@ -75,17 +70,12 @@ export const remark: Rule.RuleModule = { err.message === '`processSync` finished async. Use `process` instead' ) { - const { messages, error } = processSync( + const { messages } = processSync( fileOptions, physicalFilename, isMdx, ) - /* istanbul ignore if */ - if (error) { - file.message(error.message).fatal = true - } else { - file.messages = messages - } + file.messages = messages } else { if (!file.messages.includes(err)) { file.message(err).fatal = true diff --git a/packages/eslint-plugin-mdx/src/worker.ts b/packages/eslint-plugin-mdx/src/worker.ts index ee598e29..60388eea 100644 --- a/packages/eslint-plugin-mdx/src/worker.ts +++ b/packages/eslint-plugin-mdx/src/worker.ts @@ -1,27 +1,27 @@ -import { hasProperties } from 'eslint-mdx' import { runAsWorker } from 'synckit' import type { VFileOptions } from 'vfile' +import vfile from 'vfile' import { getRemarkProcessor } from './rules' // eslint-disable-next-line @typescript-eslint/no-floating-promises runAsWorker( - async (options: VFileOptions, physicalFilename: string, isMdx: boolean) => { + async ( + fileOptions: VFileOptions, + physicalFilename: string, + isMdx: boolean, + ) => { const remarkProcessor = getRemarkProcessor(physicalFilename, isMdx) + const file = vfile(fileOptions) try { - const vfile = await remarkProcessor.process(options) - return { - messages: vfile.messages, - } + await remarkProcessor.process(file) } catch (err) { - return { - error: { - message: hasProperties(err, ['message']) - ? err.message - : (console.error(err), - 'Fetal error without message, please check the output instead'), - }, + if (!file.messages.includes(err)) { + file.message(err).fatal = true } } + return { + messages: file.messages, + } }, ) From 23ca10fac2c7c2360dbe40c21c288f0c46bda16e Mon Sep 17 00:00:00 2001 From: JounQin Date: Tue, 27 Apr 2021 11:56:36 +0800 Subject: [PATCH 3/4] feat: cache broken sync processor --- .../eslint-plugin-mdx/src/rules/helpers.ts | 7 +- .../eslint-plugin-mdx/src/rules/remark.ts | 68 ++++++++++--------- packages/eslint-plugin-mdx/src/rules/types.ts | 14 ++++ packages/eslint-plugin-mdx/src/worker.ts | 10 +-- test/remark.test.ts | 22 +++++- 5 files changed, 78 insertions(+), 43 deletions(-) diff --git a/packages/eslint-plugin-mdx/src/rules/helpers.ts b/packages/eslint-plugin-mdx/src/rules/helpers.ts index ca1daab5..83a14b6c 100644 --- a/packages/eslint-plugin-mdx/src/rules/helpers.ts +++ b/packages/eslint-plugin-mdx/src/rules/helpers.ts @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import { cosmiconfigSync } from 'cosmiconfig' -import { arrayify, hasProperties } from 'eslint-mdx' +import { arrayify } from 'eslint-mdx' import remarkMdx from 'remark-mdx' import remarkParse from 'remark-parse' import remarkStringify from 'remark-stringify' @@ -52,10 +52,7 @@ export const getPhysicalFilename = (filename: string): string => { } } catch (err) { // https://github.com/eslint/eslint/issues/11989 - if ( - hasProperties<{ code: string }>(err, ['code']) && - err.code === 'ENOTDIR' - ) { + if ((err as { code: string }).code === 'ENOTDIR') { return getPhysicalFilename(path.dirname(filename)) } } diff --git a/packages/eslint-plugin-mdx/src/rules/remark.ts b/packages/eslint-plugin-mdx/src/rules/remark.ts index b476f47c..8df5411a 100644 --- a/packages/eslint-plugin-mdx/src/rules/remark.ts +++ b/packages/eslint-plugin-mdx/src/rules/remark.ts @@ -2,23 +2,17 @@ import path from 'path' import type { Rule } from 'eslint' import type { ParserOptions } from 'eslint-mdx' -import { - DEFAULT_EXTENSIONS, - MARKDOWN_EXTENSIONS, - hasProperties, -} from 'eslint-mdx' +import { DEFAULT_EXTENSIONS, MARKDOWN_EXTENSIONS } from 'eslint-mdx' import { createSyncFn } from 'synckit' -import type { VFile, VFileOptions } from 'vfile' +import type { FrozenProcessor } from 'unified' import vfile from 'vfile' import { getPhysicalFilename, getRemarkProcessor } from './helpers' -import type { RemarkLintMessage } from './types' +import type { ProcessSync, RemarkLintMessage } from './types' -const processSync = createSyncFn(require.resolve('../worker')) as ( - options: VFileOptions, - physicalFilename: string, - isFile: boolean, -) => Pick +const processSync = createSyncFn(require.resolve('../worker')) as ProcessSync + +const brokenCache = new WeakMap() export const remark: Rule.RuleModule = { meta: { @@ -61,24 +55,31 @@ export const remark: Rule.RuleModule = { const file = vfile(fileOptions) - try { - remarkProcessor.processSync(file) - } catch (err) { - /* istanbul ignore else */ - if ( - hasProperties(err, ['message']) && - err.message === + const broken = brokenCache.get(remarkProcessor) + + if (broken) { + const { messages } = processSync(fileOptions, physicalFilename, isMdx) + file.messages = messages + } else { + try { + remarkProcessor.processSync(file) + } catch (err) { + /* istanbul ignore else */ + if ( + (err as Error).message === '`processSync` finished async. Use `process` instead' - ) { - const { messages } = processSync( - fileOptions, - physicalFilename, - isMdx, - ) - file.messages = messages - } else { - if (!file.messages.includes(err)) { - file.message(err).fatal = true + ) { + brokenCache.set(remarkProcessor, true) + const { messages } = processSync( + fileOptions, + physicalFilename, + isMdx, + ) + file.messages = messages + } else { + if (!file.messages.includes(err)) { + file.message(err).fatal = true + } } } } @@ -130,11 +131,14 @@ export const remark: Rule.RuleModule = { end.offset == null ? start.offset + 1 : end.offset, ] const partialText = sourceText.slice(...range) - const fixed = remarkProcessor.processSync(partialText).toString() + const fixed = broken + ? processSync(partialText, physicalFilename, isMdx) + : remarkProcessor.processSync(partialText).toString() return fixer.replaceTextRange( range, - /* istanbul ignore next */ - partialText.endsWith('\n') ? fixed : fixed.slice(0, -1), // remove redundant new line + partialText.endsWith('\n') + ? /* istanbul ignore next */ fixed + : fixed.slice(0, -1), // remove redundant new line ) }, }) diff --git a/packages/eslint-plugin-mdx/src/rules/types.ts b/packages/eslint-plugin-mdx/src/rules/types.ts index ba79e6d4..b3739b21 100644 --- a/packages/eslint-plugin-mdx/src/rules/types.ts +++ b/packages/eslint-plugin-mdx/src/rules/types.ts @@ -1,6 +1,7 @@ import type { Linter } from 'eslint' import type { ExpressionStatement, Node } from 'estree' import type { Attacher } from 'unified' +import type { VFile, VFileOptions } from 'vfile' export interface WithParent { parent: NodeWithParent @@ -25,3 +26,16 @@ export interface RemarkLintMessage { ruleId: string severity: Linter.Severity } + +export interface ProcessSync { + (text: string, physicalFilename: string, isFile: boolean): string + (fileOptions: VFileOptions, physicalFilename: string, isFile: boolean): Pick< + VFile, + 'messages' + > + ( + textOrFileOptions: string | VFileOptions, + physicalFilename: string, + isFile: boolean, + ): string | VFileOptions +} diff --git a/packages/eslint-plugin-mdx/src/worker.ts b/packages/eslint-plugin-mdx/src/worker.ts index 60388eea..8dcd9a4e 100644 --- a/packages/eslint-plugin-mdx/src/worker.ts +++ b/packages/eslint-plugin-mdx/src/worker.ts @@ -7,12 +7,12 @@ import { getRemarkProcessor } from './rules' // eslint-disable-next-line @typescript-eslint/no-floating-promises runAsWorker( async ( - fileOptions: VFileOptions, + textOrFileOptions: string | VFileOptions, physicalFilename: string, isMdx: boolean, ) => { const remarkProcessor = getRemarkProcessor(physicalFilename, isMdx) - const file = vfile(fileOptions) + const file = vfile(textOrFileOptions) try { await remarkProcessor.process(file) } catch (err) { @@ -20,8 +20,8 @@ runAsWorker( file.message(err).fatal = true } } - return { - messages: file.messages, - } + return typeof textOrFileOptions === 'string' + ? file.toString() + : { messages: file.messages } }, ) diff --git a/test/remark.test.ts b/test/remark.test.ts index ebeef85b..6b9510c2 100644 --- a/test/remark.test.ts +++ b/test/remark.test.ts @@ -46,7 +46,7 @@ ruleTester.run('remark 1', remark, { }, }, { - code: '
Header5
', + code: '
Header6
', parser, parserOptions, filename: path.resolve(__dirname, 'fixtures/async/test.mdx'), @@ -73,5 +73,25 @@ ruleTester.run('remark 1', remark, { }, ], }, + { + code: '[CHANGELOG](./CHANGELOG.md)', + parser, + parserOptions, + filename: path.resolve(__dirname, 'fixtures/async/test.mdx'), + errors: [ + { + message: JSON.stringify({ + reason: 'Link to unknown file: `CHANGELOG.md`', + source: 'remark-validate-links', + ruleId: 'missing-file', + severity: 1, + }), + line: 1, + column: 1, + endLine: 1, + endColumn: 28, + }, + ], + }, ], }) From 625996b8eb24d4ce0169b7d8cbd46357d8b11b83 Mon Sep 17 00:00:00 2001 From: JounQin Date: Tue, 27 Apr 2021 19:56:36 +0800 Subject: [PATCH 4/4] fix: mark processor broken --- packages/eslint-plugin-mdx/src/rules/remark.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin-mdx/src/rules/remark.ts b/packages/eslint-plugin-mdx/src/rules/remark.ts index 8df5411a..423d2968 100644 --- a/packages/eslint-plugin-mdx/src/rules/remark.ts +++ b/packages/eslint-plugin-mdx/src/rules/remark.ts @@ -55,7 +55,7 @@ export const remark: Rule.RuleModule = { const file = vfile(fileOptions) - const broken = brokenCache.get(remarkProcessor) + let broken = brokenCache.get(remarkProcessor) if (broken) { const { messages } = processSync(fileOptions, physicalFilename, isMdx) @@ -69,7 +69,7 @@ export const remark: Rule.RuleModule = { (err as Error).message === '`processSync` finished async. Use `process` instead' ) { - brokenCache.set(remarkProcessor, true) + brokenCache.set(remarkProcessor, (broken = true)) const { messages } = processSync( fileOptions, physicalFilename,