React19: Add a validator to check plugin compatibility#552
React19: Add a validator to check plugin compatibility#552leventebalogh wants to merge 5 commits intomainfrom
Conversation
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.
| 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{ | ||
| ®exDetector{regex: regexp.MustCompile(`ref:"[^"]+?"`)}, | ||
| ®exDetector{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")}, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
+1 all this can work with semgrep and we even have already one for this case
academo
left a comment
There was a problem hiding this comment.
I think most if not all of this check can be a semgrep rule.
|
That's a totally good point, thanks guys, I'll run another circle with this 👍 |
sunker
left a comment
There was a problem hiding this comment.
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
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
What changed?
Adds a
reactcompatanalyzer that shells out tonpx @grafana/react-detect --jsonto 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 (viaarchive.Analyzer), so it works without source code. The detection logic lives in@grafana/react-detect— not duplicated here.How it works:
archive.Analyzerto get the extracted plugin directorydist/symlink (what react-detect expects)npx -y @grafana/react-detect@latest --jsonwith a 60s timeoutnpxis 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.