Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4d3d495
Add React Conf 2024 to /videos (#7977)
rickhanlonii Sep 9, 2025
0bfd418
docs: add flushSync Troubleshooting section (#7978)
Akshay090 Sep 11, 2025
a5181c2
Bump Activity docs to canary (#7974)
rickhanlonii Sep 12, 2025
5b9a2ce
Upgrade babel-plugin-react-compiler (#7985)
poteto Sep 16, 2025
a566d87
Add copyright script (#7991)
poteto Sep 18, 2025
bd03b86
Update copyright on all files (#7992)
poteto Sep 18, 2025
b6a32d1
Add local eslint rule to validate markdown codeblocks with React Comp…
poteto Sep 18, 2025
5cc9b7b
fix compiler errors (#7989)
poteto Sep 18, 2025
2a9ef2d
Revert "fix compiler errors (#7989)" (#7995)
poteto Sep 18, 2025
f369f3e
Ignore braces when building Sandpack file map (#7996)
poteto Sep 18, 2025
730d045
Restore lint corrections from #7989 (#7997)
poteto Sep 18, 2025
c15e20f
Install eslint-local-rules as postinstall (#7993)
poteto Sep 19, 2025
366b5fb
Add new eslint rule reference docs (#7986)
poteto Sep 19, 2025
c8211fc
Add RC badge to RC-only lint rules (#8005)
rickhanlonii Sep 22, 2025
170e5b9
Remove unnecessary spaces from useCallback.md (#8006)
rammba Sep 23, 2025
790625f
Fix ViewTransition examples (#7999)
EugeneChoi4 Sep 23, 2025
1b20061
Fix value wrongly formatted as code in useDeferredValue.md (#8024)
rammba Sep 26, 2025
49c2d26
Update useEffectEvent docs for canary (#8025)
jackpope Sep 28, 2025
c60922f
Fix incorrect negative in use-memo intro (#8027)
karlhorky Oct 1, 2025
3f0235b
Docs for `cacheSignal` (#8023)
eps1lon Oct 1, 2025
9fddeca
docs: initial docs for Performance tracks (#7938)
hoxyq Oct 1, 2025
aba6b86
Docs for partial-prerendering APIs (#7869)
rickhanlonii Oct 1, 2025
10e5ad5
Conditionally deriving state is allowed (#8033)
gaearon Oct 1, 2025
dc86f88
Overview for React Server Performance tracks (#8031)
eps1lon Oct 1, 2025
c3d7560
Blog post for React 19.2 (#8028)
rickhanlonii Oct 1, 2025
0a803f6
Update eslint-plugin-react-hooks config docs (#8030)
jackpope Oct 1, 2025
f9fd07c
Fix Activity sandboxes (#8035)
rickhanlonii Oct 1, 2025
37f862a
Latest release is 19.2 (#8040)
eps1lon Oct 2, 2025
9850724
Upgrade sandboxes to 19.2 (#8037)
gaearon Oct 2, 2025
42037e2
Few cleanups for 19.2 (#8046)
rickhanlonii Oct 2, 2025
07b7a5d
s/19.3/19.2
rickhanlonii Oct 2, 2025
996ef72
Prerender can be aborted in stable (#8039)
eps1lon Oct 2, 2025
775d895
fix typo
sophiebits Oct 3, 2025
ae584af
Add experimental docs for Fragment refs (#8010)
jackpope Oct 3, 2025
7b8612e
Update eslint-plugin-react-hooks version reference (#8051)
poteto Oct 3, 2025
11cb6b5
Clarify eslint-plugin-react-hooks configuration details (#8052)
poteto Oct 4, 2025
c6f1ea6
merging all conflicts
react-translations-bot Oct 6, 2025
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
24 changes: 21 additions & 3 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,36 @@
"root": true,
"extends": "next/core-web-vitals",
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "eslint-plugin-react-compiler"],
"plugins": ["@typescript-eslint", "eslint-plugin-react-compiler", "local-rules"],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", {"varsIgnorePattern": "^_"}],
"react-hooks/exhaustive-deps": "error",
"react/no-unknown-property": ["error", {"ignore": ["meta"]}],
"react-compiler/react-compiler": "error"
"react-compiler/react-compiler": "error",
"local-rules/lint-markdown-code-blocks": "error"
},
"env": {
"node": true,
"commonjs": true,
"browser": true,
"es6": true
}
},
"overrides": [
{
"files": ["src/content/**/*.md"],
"parser": "./eslint-local-rules/parser",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
"react-hooks/exhaustive-deps": "off",
"react/no-unknown-property": "off",
"react-compiler/react-compiler": "off",
"local-rules/lint-markdown-code-blocks": "error"
}
}
]
}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
node_modules
/.pnp
.pnp.js

Expand Down
7 changes: 7 additions & 0 deletions colors.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/**
* 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.
*/

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
```jsx
import {useState} from 'react';
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1);
return <div>{count}</div>;
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
```jsx title="Counter" {expectedErrors: {'react-compiler': [99]}} {expectedErrors: {'react-compiler': [2]}}
import {useState} from 'react';
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1);
return <div>{count}</div>;
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
```jsx {expectedErrors: {'react-compiler': 'invalid'}}
import {useState} from 'react';
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1);
return <div>{count}</div>;
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```bash
setCount()
```

```txt
import {useState} from 'react';
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
```jsx {expectedErrors: {'react-compiler': [3]}}
function Hello() {
return <h1>Hello</h1>;
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
```jsx {expectedErrors: {'react-compiler': [4]}}
import {useState} from 'react';
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1);
return <div>{count}</div>;
}
```
131 changes: 131 additions & 0 deletions eslint-local-rules/__tests__/lint-markdown-code-blocks.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* 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 assert = require('assert');
const fs = require('fs');
const path = require('path');
const {ESLint} = require('eslint');
const plugin = require('..');

const FIXTURES_DIR = path.join(
__dirname,
'fixtures',
'src',
'content'
);
const PARSER_PATH = path.join(__dirname, '..', 'parser.js');

function createESLint({fix = false} = {}) {
return new ESLint({
useEslintrc: false,
fix,
plugins: {
'local-rules': plugin,
},
overrideConfig: {
parser: PARSER_PATH,
plugins: ['local-rules'],
rules: {
'local-rules/lint-markdown-code-blocks': 'error',
},
parserOptions: {
sourceType: 'module',
},
},
});
}

function readFixture(name) {
return fs.readFileSync(path.join(FIXTURES_DIR, name), 'utf8');
}

async function lintFixture(name, {fix = false} = {}) {
const eslint = createESLint({fix});
const filePath = path.join(FIXTURES_DIR, name);
const markdown = readFixture(name);
const [result] = await eslint.lintText(markdown, {filePath});
return result;
}

async function run() {
const basicResult = await lintFixture('basic-error.md');
assert.strictEqual(
basicResult.messages.length,
1,
'expected one diagnostic'
);
assert(
basicResult.messages[0].message.includes('Calling setState during render'),
'expected message to mention setState during render'
);

const suppressedResult = await lintFixture('suppressed-error.md');
assert.strictEqual(
suppressedResult.messages.length,
0,
'expected suppression metadata to silence diagnostic'
);

const staleResult = await lintFixture('stale-expected-error.md');
assert.strictEqual(
staleResult.messages.length,
1,
'expected stale metadata error'
);
assert.strictEqual(
staleResult.messages[0].message,
'React Compiler expected error on line 3 was not triggered'
);

const duplicateResult = await lintFixture('duplicate-metadata.md');
assert.strictEqual(
duplicateResult.messages.length,
2,
'expected duplicate metadata to surface compiler diagnostic and stale metadata notice'
);
const duplicateFixed = await lintFixture('duplicate-metadata.md', {
fix: true,
});
assert(
duplicateFixed.output.includes(
"{expectedErrors: {'react-compiler': [4]}}"
),
'expected duplicates to be rewritten to a single canonical block'
);
assert(
!duplicateFixed.output.includes('[99]'),
'expected stale line numbers to be removed from metadata'
);

const mixedLanguageResult = await lintFixture('mixed-language.md');
assert.strictEqual(
mixedLanguageResult.messages.length,
0,
'expected non-js code fences to be ignored'
);

const malformedResult = await lintFixture('malformed-metadata.md');
assert.strictEqual(
malformedResult.messages.length,
1,
'expected malformed metadata to fall back to compiler diagnostics'
);
const malformedFixed = await lintFixture('malformed-metadata.md', {
fix: true,
});
assert(
malformedFixed.output.includes(
"{expectedErrors: {'react-compiler': [4]}}"
),
'expected malformed metadata to be replaced with canonical form'
);
}

run().catch(error => {
console.error(error);
process.exitCode = 1;
});
14 changes: 14 additions & 0 deletions eslint-local-rules/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* 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 lintMarkdownCodeBlocks = require('./rules/lint-markdown-code-blocks');

module.exports = {
rules: {
'lint-markdown-code-blocks': lintMarkdownCodeBlocks,
},
};
12 changes: 12 additions & 0 deletions eslint-local-rules/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "eslint-plugin-local-rules",
"version": "0.0.0",
"main": "index.js",
"private": "true",
"scripts": {
"test": "node __tests__/lint-markdown-code-blocks.test.js"
},
"devDependencies": {
"eslint-mdx": "^2"
}
}
8 changes: 8 additions & 0 deletions eslint-local-rules/parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* 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.
*/

module.exports = require('eslint-mdx');
77 changes: 77 additions & 0 deletions eslint-local-rules/rules/diagnostics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* 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.
*/

function getRelativeLine(loc) {
return loc?.start?.line ?? loc?.line ?? 1;
}

function getRelativeColumn(loc) {
return loc?.start?.column ?? loc?.column ?? 0;
}

function getRelativeEndLine(loc, fallbackLine) {
if (loc?.end?.line != null) {
return loc.end.line;
}
if (loc?.line != null) {
return loc.line;
}
return fallbackLine;
}

function getRelativeEndColumn(loc, fallbackColumn) {
if (loc?.end?.column != null) {
return loc.end.column;
}
if (loc?.column != null) {
return loc.column;
}
return fallbackColumn;
}

/**
* @param {import('./markdown').MarkdownCodeBlock} block
* @param {Array<{detail: any, loc: any, message: string}>} diagnostics
* @returns {Array<{detail: any, message: string, relativeStartLine: number, markdownLoc: {start: {line: number, column: number}, end: {line: number, column: number}}}>}
*/
function normalizeDiagnostics(block, diagnostics) {
return diagnostics.map(({detail, loc, message}) => {
const relativeStartLine = Math.max(getRelativeLine(loc), 1);
const relativeStartColumn = Math.max(getRelativeColumn(loc), 0);
const relativeEndLine = Math.max(
getRelativeEndLine(loc, relativeStartLine),
relativeStartLine
);
const relativeEndColumn = Math.max(
getRelativeEndColumn(loc, relativeStartColumn),
relativeStartColumn
);

const markdownStartLine = block.codeStartLine + relativeStartLine - 1;
const markdownEndLine = block.codeStartLine + relativeEndLine - 1;

return {
detail,
message,
relativeStartLine,
markdownLoc: {
start: {
line: markdownStartLine,
column: relativeStartColumn,
},
end: {
line: markdownEndLine,
column: relativeEndColumn,
},
},
};
});
}

module.exports = {
normalizeDiagnostics,
};
Loading
Loading