Files
react.dev/eslint-local-rules/rules/diagnostics.js
lauren b6a32d1e0e Add local eslint rule to validate markdown codeblocks with React Compiler (#7988)
In https://github.com/facebook/react/pull/34462 for example, we found an issue where the compiler was incorrectly validating an example straight from the docs.

In order to find more issues like this + also provide more feedback to doc authors on valid/invalid patterns, this PR adds a new local eslint rule which validates all markdown codeblocks containing components/hooks with React Compiler. An autofixer is also provided.

To express that a codeblock has an expected error, we can use the following metadata:

```ts
// pseudo type def
type MarkdownCodeBlockMetadata = {
    expectedErrors?: {
      'react-compiler'?: number[];
    };
  };
```

and can be used like so:

````
```js {expectedErrors: {'react-compiler': [4]}}
//  setState directly in render
function Component({value}) {
  const [count, setCount] = useState(0);
  setCount(value); // error on L4
  return <div>{count}</div>;
}
```
````

Because this is defined as a local rule, we don't have the same granular reporting that `eslint-plugin-react-hooks` yet. I can look into that later but for now this first PR just sets us up with something basic.
2025-09-18 15:32:18 -04:00

78 lines
2.0 KiB
JavaScript

/**
* 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,
};