Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```js [[1, 1, "submitAction"]]
function UpdateName() {}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```js [[1, 3, "submitAction"]]
function submitAction() {}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```js [[1, 1, "submitAction"]]
function submitAction() {}
```

```js [[1, 1, "\\"hidden\\""]]
const mode = "hidden";
```
31 changes: 31 additions & 0 deletions eslint-local-rules/__tests__/lint-markdown-code-blocks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,37 @@ async function run() {
),
'expected malformed metadata to be replaced with canonical form'
);

const validHighlightResult = await lintFixture('valid-highlight.md');
assert.strictEqual(
validHighlightResult.messages.length,
0,
'expected a valid inline highlight to pass'
);

const missingHighlightResult = await lintFixture('missing-highlight.md');
assert.strictEqual(
missingHighlightResult.messages.length,
1,
'expected a highlight with text missing from its line to fail'
);
assert.strictEqual(
missingHighlightResult.messages[0].message,
"Could not find 'submitAction' on highlighted line 1"
);

const outOfBoundsHighlightResult = await lintFixture(
'out-of-bounds-highlight.md'
);
assert.strictEqual(
outOfBoundsHighlightResult.messages.length,
1,
'expected an out-of-bounds highlight line to fail'
);
assert.strictEqual(
outOfBoundsHighlightResult.messages[0].message,
'Code highlight line 3 is outside this code block'
);
}

run().catch(error => {
Expand Down
66 changes: 66 additions & 0 deletions eslint-local-rules/rules/inline-highlights.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

const INLINE_HIGHLIGHT_REGEX = /(\[\[.*\]\])/;

function validateInlineHighlights(meta, code) {
const match = INLINE_HIGHLIGHT_REGEX.exec(meta);
if (!match) {
return [];
}

let highlights;
try {
highlights = JSON.parse(match[1]);
} catch (error) {
return ['Code highlight metadata must be valid JSON'];
}

if (!Array.isArray(highlights)) {
return ['Code highlight metadata must be an array'];
}

const lines = code.split('\n');
const errors = [];

for (const highlight of highlights) {
if (!Array.isArray(highlight) || highlight.length < 3) {
errors.push('Each code highlight must specify a step, line, and text');
continue;
}

const [, lineNo, text, fromIndex] = highlight;
if (!Number.isInteger(lineNo) || lineNo < 1 || lineNo > lines.length) {
errors.push(`Code highlight line ${lineNo} is outside this code block`);
continue;
}
if (typeof text !== 'string') {
errors.push(`Code highlight text on line ${lineNo} must be a string`);
continue;
}

const line = lines[lineNo - 1];
let index = line.indexOf(text);
const lastIndex = line.lastIndexOf(text);
if (index !== lastIndex) {
if (fromIndex === undefined) {
errors.push(
`Found '${text}' twice on highlighted line ${lineNo}; specify fromIndex`
);
continue;
}
index = line.indexOf(text, fromIndex);
}
if (index === -1) {
errors.push(`Could not find '${text}' on highlighted line ${lineNo}`);
}
}

return errors;
}

module.exports = {validateInlineHighlights};
14 changes: 13 additions & 1 deletion eslint-local-rules/rules/lint-markdown-code-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ const {
setCompilerExpectedLines,
} = require('./metadata');
const {normalizeDiagnostics} = require('./diagnostics');
const {validateInlineHighlights} = require('./inline-highlights');
const {parseMarkdownFile} = require('./markdown');
const {runReactCompiler} = require('./react-compiler');

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Run React Compiler on markdown code blocks',
description: 'Validate and compile markdown code blocks',
category: 'Possible Errors',
},
fixable: 'code',
Expand All @@ -43,6 +44,17 @@ module.exports = {
const {blocks} = parseMarkdownFile(sourceCode.text, filename);
// For each supported code block, run the compiler and reconcile metadata.
for (const block of blocks) {
for (const message of validateInlineHighlights(
block.meta,
block.code
)) {
context.report({
node,
loc: block.position,
message,
});
}

const compilerResult = runReactCompiler(
block.code,
`${filename}#codeblock`
Expand Down
2 changes: 2 additions & 0 deletions eslint-local-rules/rules/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const {parseFenceMetadata} = require('./metadata');
* @property {{lineIndex: number, rawText: string, metaText: string, range: [number, number]}} fence
* @property {string} filePath
* @property {string} lang
* @property {string} meta
* @property {import('./metadata').FenceMetadata} metadata
*/

Expand Down Expand Up @@ -76,6 +77,7 @@ function parseMarkdownFile(content, filePath) {

blocks.push({
lang: rawLang || normalizedLang,
meta: node.meta || metaText.trim(),
metadata,
filePath,
code: node.value || '',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"prettier:diff": "yarn nit:source",
"lint-heading-ids": "node scripts/headingIdLinter.js",
"fix-headings": "node scripts/headingIdLinter.js --fix",
"ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids rss deadlinks",
"ci-check": "npm-run-all prettier:diff --parallel lint test:eslint-local-rules tsc lint-heading-ids rss deadlinks",
"tsc": "tsc --noEmit",
"start": "next start",
"postinstall": "yarn --cwd eslint-local-rules install && is-ci || husky install .husky",
Expand Down
Loading