Skip to content

React19: Add a validator to check plugin compatibility#552

Draft
leventebalogh wants to merge 5 commits intomainfrom
leventebalogh/react19-compat-check
Draft

React19: Add a validator to check plugin compatibility#552
leventebalogh wants to merge 5 commits intomainfrom
leventebalogh/react19-compat-check

Conversation

@leventebalogh
Copy link
Copy Markdown
Contributor

@leventebalogh leventebalogh commented Mar 30, 2026

What changed?

Adds a reactcompat analyzer that shells out to npx @grafana/react-detect --json to detect React 19 compatibility issues in plugin bundles.

Why a separate analyzer instead of semgrep rules?
Semgrep runs against source code (via sourcecode.Analyzer), which requires the plugin's Git repo URL. Many plugin submissions only include the built archive. This analyzer runs against the built bundle + sourcemaps (via archive.Analyzer), so it works without source code. The detection logic lives in @grafana/react-detect — not duplicated here.

How it works:

  • Depends on archive.Analyzer to get the extracted plugin directory
  • Creates a temp dir with a dist/ symlink (what react-detect expects)
  • Runs npx -y @grafana/react-detect@latest --json with a 60s timeout
  • Parses the JSON output and maps issues to warning-level diagnostics
  • Silently skips if npx is not available (graceful degradation)

All rules emit warnings (not errors) and include a false-positive disclaimer, since the tool analyses sourcemaps which can be imprecise.

Scan plugin module.js bundles for patterns that indicate incompatibility
with React 19: removed PropTypes/defaultProps, legacy context, string
refs, ReactDOM.render, ReactDOM.findDOMNode, legacy lifecycle methods,
and removed React.createFactory.
@cla-assistant
Copy link
Copy Markdown

cla-assistant bot commented Mar 30, 2026

CLA assistant check
All committers have signed the CLA.

@leventebalogh leventebalogh changed the title feat: add React 19 compatibility checker for plugins React19: Add a validator to check plugin compatibility Mar 30, 2026
@leventebalogh leventebalogh self-assigned this Mar 30, 2026
@leventebalogh leventebalogh moved this from 📬 Triage to 🔬 In review in Grafana Catalog Team Mar 30, 2026
Comment on lines +83 to +144
var reactPatterns = []reactPattern{
{
rule: react19PropTypes,
title: "module.js: Uses removed React API propTypes or defaultProps",
description: "Detected usage of '%s'. propTypes and defaultProps on function components were removed in React 19.",
detectors: []detector{
&containsBytesDetector{pattern: []byte(".propTypes=")},
&containsBytesDetector{pattern: []byte(".defaultProps=")},
},
},
{
rule: react19LegacyContext,
title: "module.js: Uses removed React legacy context API",
description: "Detected usage of '%s'. contextTypes, childContextTypes, and getChildContext were removed in React 19.",
detectors: []detector{
&containsBytesDetector{pattern: []byte(".contextTypes=")},
&containsBytesDetector{pattern: []byte(".childContextTypes=")},
&containsBytesDetector{pattern: []byte("getChildContext")},
},
},
{
rule: react19StringRefs,
title: "module.js: Uses removed React string refs",
description: "Detected usage of '%s'. String refs were removed in React 19. Use callback refs or React.createRef() instead.",
detectors: []detector{
&regexDetector{regex: regexp.MustCompile(`ref:"[^"]+?"`)},
&regexDetector{regex: regexp.MustCompile(`ref:'[^']+'`)},
},
},
{
rule: react19CreateFactory,
title: "module.js: Uses removed React.createFactory",
description: "Detected usage of '%s'. React.createFactory was removed in React 19. Use JSX instead.",
detectors: []detector{
&containsBytesDetector{pattern: []byte("createFactory(")},
},
},
{
rule: react19FindDOMNode,
title: "module.js: Uses removed ReactDOM.findDOMNode",
description: "Detected usage of '%s'. ReactDOM.findDOMNode was removed in React 19. Use DOM refs instead.",
detectors: []detector{
&containsBytesDetector{pattern: []byte("findDOMNode(")},
},
},
{
rule: react19LegacyRender,
title: "module.js: Uses removed ReactDOM.render or unmountComponentAtNode",
description: "Detected usage of '%s'. ReactDOM.render and unmountComponentAtNode were removed in React 19. Use createRoot instead.",
detectors: []detector{
&containsBytesDetector{pattern: []byte("ReactDOM.render(")},
&containsBytesDetector{pattern: []byte("unmountComponentAtNode(")},
},
},
{
rule: react19SecretInternals,
title: "module.js: Uses React internal __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED",
description: "Detected usage of '%s'. This internal was removed in React 19.",
detectors: []detector{
&containsBytesDetector{pattern: []byte("__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED")},
},
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see, these could be semgrep rules? If that's the case there is no need to create a new rule

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 all this can work with semgrep and we even have already one for this case

- pattern-regex: __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED

Copy link
Copy Markdown
Collaborator

@academo academo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think most if not all of this check can be a semgrep rule.

@leventebalogh
Copy link
Copy Markdown
Contributor Author

That's a totally good point, thanks guys, I'll run another circle with this 👍

Copy link
Copy Markdown

@sunker sunker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to flag that Jack's @grafana/react-detect package does exactly this kind of analysis but with a few extras rules. It's published to npm and can be invoked with npx @grafana/react-detect. It also has a --json output mode that could work well for feeding results back into the validator. I think we should use this instead.

Replace the in-process regex-based React 19 compatibility checker with
a shell-out to npx @grafana/react-detect. This delegates detection logic
to the upstream package so rules are maintained in a single place.

Key changes:
- Dependency changed from modulejs.Analyzer to archive.Analyzer
- Runs npx -y @grafana/react-detect@latest --json against the archive
- Creates temp dir with dist/ symlink (what react-detect expects)
- Dynamic rules from tool output, respecting react19Issue.Disabled config
- Graceful skip when npx is not in PATH
- 60s timeout, stderr capture for debug logging
@leventebalogh leventebalogh marked this pull request as draft April 1, 2026 15:57
The yesoreyeram-infinity-datasource test case was missing the expected
provenance recommendation diagnostic, causing the integration test to
fail. This was a pre-existing issue on main, not introduced by this PR.
The genreadme test requires the README.md analyzers table to be in sync
with registered analyzers. Also revert the incorrect provenance test
expectation - the provenance check does not run in Docker CI.
…tput order

- Add why-comment on prepareTmpDir explaining archive vs dist/ layout
- Add why-comment on react19Issue.Disabled explaining it gates all dynamic rules
- Sort source code issue patterns for deterministic diagnostic order
- Append false-positive disclaimer to all diagnostic details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 🔬 In review

Development

Successfully merging this pull request may close these issues.

4 participants