@grafana/react-detect 0.2.1 → 0.3.0-canary.2395.21023152965.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/detect19.js +24 -15
- package/dist/utils/analyzer.js +5 -5
- package/dist/utils/dependencies.js +17 -7
- package/package.json +2 -2
- package/src/commands/detect19.ts +28 -19
- package/src/utils/analyzer.ts +10 -3
- package/src/utils/dependencies.ts +22 -7
|
@@ -5,25 +5,34 @@ import { jsonReporter } from '../reporters/json.js';
|
|
|
5
5
|
import { consoleReporter } from '../reporters/console.js';
|
|
6
6
|
import { extractAllSources } from '../source-extractor.js';
|
|
7
7
|
import { analyzeSourceFiles } from '../analyzer.js';
|
|
8
|
+
import { output } from '../utils/output.js';
|
|
8
9
|
|
|
9
10
|
async function detect19(argv) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
try {
|
|
12
|
+
const pluginRoot = argv.pluginRoot || process.cwd();
|
|
13
|
+
const allMatches = await getAllMatches(pluginRoot);
|
|
14
|
+
const depContext = new DependencyContext();
|
|
15
|
+
await depContext.loadDependencies(pluginRoot);
|
|
16
|
+
const matchesWithRootDependency = allMatches.map((match) => {
|
|
17
|
+
if (match.type === "dependency" && match.packageName) {
|
|
18
|
+
return { ...match, rootDependency: depContext.findRootDependency(match.packageName) };
|
|
19
|
+
}
|
|
20
|
+
return match;
|
|
21
|
+
});
|
|
22
|
+
const results = generateAnalysisResults(matchesWithRootDependency, pluginRoot, depContext);
|
|
23
|
+
if (argv.json) {
|
|
24
|
+
jsonReporter(results);
|
|
25
|
+
} else {
|
|
26
|
+
consoleReporter(results);
|
|
17
27
|
}
|
|
18
|
-
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
process.exit(results.summary.totalIssues > 0 ? 1 : 0);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
output.error({
|
|
31
|
+
title: "Error during detection",
|
|
32
|
+
body: [error.message]
|
|
33
|
+
});
|
|
34
|
+
process.exit(1);
|
|
25
35
|
}
|
|
26
|
-
process.exit(results.summary.totalIssues > 0 ? 1 : 0);
|
|
27
36
|
}
|
|
28
37
|
async function getAllMatches(pluginRoot) {
|
|
29
38
|
const sourcemapPaths = await findSourceMapFiles(pluginRoot);
|
package/dist/utils/analyzer.js
CHANGED
|
@@ -37,7 +37,7 @@ function analyzeComponentType(ast) {
|
|
|
37
37
|
function hasReactComponent(ast) {
|
|
38
38
|
let found = false;
|
|
39
39
|
walk(ast, (node) => {
|
|
40
|
-
if (node.type === "ClassDeclaration" && node.superClass?.type === "MemberExpression" && node.superClass.object.type === "Identifier" && node.superClass.object.name === "React" && node.superClass.property.type === "Identifier" && (node.superClass.property.name === "Component" || node.superClass.property.name === "PureComponent")) {
|
|
40
|
+
if (node && node.type === "ClassDeclaration" && node.superClass?.type === "MemberExpression" && node.superClass.object.type === "Identifier" && node.superClass.object.name === "React" && node.superClass.property.type === "Identifier" && (node.superClass.property.name === "Component" || node.superClass.property.name === "PureComponent")) {
|
|
41
41
|
found = true;
|
|
42
42
|
}
|
|
43
43
|
});
|
|
@@ -51,7 +51,7 @@ function hasFunctionComponentPattern(ast) {
|
|
|
51
51
|
}
|
|
52
52
|
let found = false;
|
|
53
53
|
walk(ast, (node) => {
|
|
54
|
-
if (node.type === "FunctionDeclaration" || node.type === "ArrowFunctionExpression") {
|
|
54
|
+
if (node && (node.type === "FunctionDeclaration" || node.type === "ArrowFunctionExpression")) {
|
|
55
55
|
found = true;
|
|
56
56
|
}
|
|
57
57
|
});
|
|
@@ -60,7 +60,7 @@ function hasFunctionComponentPattern(ast) {
|
|
|
60
60
|
function hasReactImport(ast) {
|
|
61
61
|
let found = false;
|
|
62
62
|
walk(ast, (node) => {
|
|
63
|
-
if (node.type === "ImportDeclaration" && node.source.type === "Literal" && (node.source.value === "react" || node.source.value === "react-dom")) {
|
|
63
|
+
if (node && node.type === "ImportDeclaration" && node.source.type === "Literal" && (node.source.value === "react" || node.source.value === "react-dom")) {
|
|
64
64
|
found = true;
|
|
65
65
|
}
|
|
66
66
|
});
|
|
@@ -69,7 +69,7 @@ function hasReactImport(ast) {
|
|
|
69
69
|
function hasJSX(ast) {
|
|
70
70
|
let found = false;
|
|
71
71
|
walk(ast, (node) => {
|
|
72
|
-
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
72
|
+
if (node && (node.type === "JSXElement" || node.type === "JSXFragment")) {
|
|
73
73
|
found = true;
|
|
74
74
|
}
|
|
75
75
|
});
|
|
@@ -78,7 +78,7 @@ function hasJSX(ast) {
|
|
|
78
78
|
function hasHooks(ast) {
|
|
79
79
|
let found = false;
|
|
80
80
|
walk(ast, (node) => {
|
|
81
|
-
if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name.startsWith("use")) {
|
|
81
|
+
if (node && node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name.startsWith("use")) {
|
|
82
82
|
found = true;
|
|
83
83
|
}
|
|
84
84
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { parsePnpmProject, parseNpmLockV2Project, parseYarnLockV2Project } from 'snyk-nodejs-lockfile-parser';
|
|
2
|
+
import { parsePnpmProject, parseNpmLockV2Project, parseYarnLockV1Project, parseYarnLockV2Project } from 'snyk-nodejs-lockfile-parser';
|
|
3
3
|
import { readFileSync, existsSync } from 'node:fs';
|
|
4
4
|
import { readJsonFile } from './plugin.js';
|
|
5
5
|
|
|
@@ -34,12 +34,22 @@ class DependencyContext {
|
|
|
34
34
|
pruneCycles: false
|
|
35
35
|
});
|
|
36
36
|
} else if (lockfile === "yarn.lock") {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
if (/#\s*yarn lockfile v1/i.test(lockfileContent)) {
|
|
38
|
+
this.depGraph = await parseYarnLockV1Project(pkgJsonContentString, lockfileContent, {
|
|
39
|
+
includeDevDeps: true,
|
|
40
|
+
includeOptionalDeps: true,
|
|
41
|
+
includePeerDeps: true,
|
|
42
|
+
strictOutOfSync: false,
|
|
43
|
+
pruneLevel: "withinTopLevelDeps"
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
this.depGraph = await parseYarnLockV2Project(pkgJsonContentString, lockfileContent, {
|
|
47
|
+
includeDevDeps: true,
|
|
48
|
+
includeOptionalDeps: true,
|
|
49
|
+
strictOutOfSync: false,
|
|
50
|
+
pruneWithinTopLevelDeps: true
|
|
51
|
+
});
|
|
52
|
+
}
|
|
43
53
|
}
|
|
44
54
|
if (pkgJsonContent.dependencies) {
|
|
45
55
|
Object.entries(pkgJsonContent.dependencies).forEach(([name, version]) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/react-detect",
|
|
3
3
|
"description": "Run various checks to detect if a Grafana plugin is compatible with React.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0-canary.2395.21023152965.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"directory": "packages/react-detect",
|
|
7
7
|
"url": "https://github.com/grafana/plugin-tools"
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"engines": {
|
|
41
41
|
"node": ">=20"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "2d906bece6457dd89becbc6c62a78ccfbdedd8f7"
|
|
44
44
|
}
|
package/src/commands/detect19.ts
CHANGED
|
@@ -6,32 +6,41 @@ import { jsonReporter } from '../reporters/json.js';
|
|
|
6
6
|
import { consoleReporter } from '../reporters/console.js';
|
|
7
7
|
import { extractAllSources } from '../source-extractor.js';
|
|
8
8
|
import { analyzeSourceFiles } from '../analyzer.js';
|
|
9
|
+
import { output } from '../utils/output.js';
|
|
9
10
|
/**
|
|
10
11
|
* Main detect command for finding React 19 breaking changes
|
|
11
12
|
*/
|
|
12
13
|
export async function detect19(argv: minimist.ParsedArgs) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
try {
|
|
15
|
+
const pluginRoot = argv.pluginRoot || process.cwd();
|
|
16
|
+
|
|
17
|
+
const allMatches = await getAllMatches(pluginRoot);
|
|
18
|
+
const depContext = new DependencyContext();
|
|
19
|
+
await depContext.loadDependencies(pluginRoot);
|
|
20
|
+
|
|
21
|
+
const matchesWithRootDependency = allMatches.map((match) => {
|
|
22
|
+
if (match.type === 'dependency' && match.packageName) {
|
|
23
|
+
return { ...match, rootDependency: depContext.findRootDependency(match.packageName) };
|
|
24
|
+
}
|
|
25
|
+
return match;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const results = generateAnalysisResults(matchesWithRootDependency, pluginRoot, depContext);
|
|
29
|
+
|
|
30
|
+
if (argv.json) {
|
|
31
|
+
jsonReporter(results);
|
|
32
|
+
} else {
|
|
33
|
+
consoleReporter(results);
|
|
22
34
|
}
|
|
23
|
-
return match;
|
|
24
|
-
});
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
process.exit(results.summary.totalIssues > 0 ? 1 : 0);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
output.error({
|
|
39
|
+
title: 'Error during detection',
|
|
40
|
+
body: [(error as Error).message],
|
|
41
|
+
});
|
|
42
|
+
process.exit(1);
|
|
32
43
|
}
|
|
33
|
-
|
|
34
|
-
process.exit(results.summary.totalIssues > 0 ? 1 : 0);
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
async function getAllMatches(pluginRoot: string) {
|
package/src/utils/analyzer.ts
CHANGED
|
@@ -47,6 +47,7 @@ export function hasReactComponent(ast: TSESTree.Program): boolean {
|
|
|
47
47
|
|
|
48
48
|
walk(ast, (node) => {
|
|
49
49
|
if (
|
|
50
|
+
node &&
|
|
50
51
|
node.type === 'ClassDeclaration' &&
|
|
51
52
|
node.superClass?.type === 'MemberExpression' &&
|
|
52
53
|
node.superClass.object.type === 'Identifier' &&
|
|
@@ -71,7 +72,7 @@ export function hasFunctionComponentPattern(ast: TSESTree.Program): boolean {
|
|
|
71
72
|
|
|
72
73
|
let found = false;
|
|
73
74
|
walk(ast, (node) => {
|
|
74
|
-
if (node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression') {
|
|
75
|
+
if (node && (node.type === 'FunctionDeclaration' || node.type === 'ArrowFunctionExpression')) {
|
|
75
76
|
found = true;
|
|
76
77
|
}
|
|
77
78
|
});
|
|
@@ -84,6 +85,7 @@ function hasReactImport(ast: TSESTree.Program): boolean {
|
|
|
84
85
|
|
|
85
86
|
walk(ast, (node) => {
|
|
86
87
|
if (
|
|
88
|
+
node &&
|
|
87
89
|
node.type === 'ImportDeclaration' &&
|
|
88
90
|
node.source.type === 'Literal' &&
|
|
89
91
|
(node.source.value === 'react' || node.source.value === 'react-dom')
|
|
@@ -99,7 +101,7 @@ function hasJSX(ast: TSESTree.Program): boolean {
|
|
|
99
101
|
let found = false;
|
|
100
102
|
|
|
101
103
|
walk(ast, (node) => {
|
|
102
|
-
if (node.type === 'JSXElement' || node.type === 'JSXFragment') {
|
|
104
|
+
if (node && (node.type === 'JSXElement' || node.type === 'JSXFragment')) {
|
|
103
105
|
found = true;
|
|
104
106
|
}
|
|
105
107
|
});
|
|
@@ -111,7 +113,12 @@ function hasHooks(ast: TSESTree.Program): boolean {
|
|
|
111
113
|
let found = false;
|
|
112
114
|
|
|
113
115
|
walk(ast, (node) => {
|
|
114
|
-
if (
|
|
116
|
+
if (
|
|
117
|
+
node &&
|
|
118
|
+
node.type === 'CallExpression' &&
|
|
119
|
+
node.callee.type === 'Identifier' &&
|
|
120
|
+
node.callee.name.startsWith('use')
|
|
121
|
+
) {
|
|
115
122
|
found = true;
|
|
116
123
|
}
|
|
117
124
|
});
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
parsePnpmProject,
|
|
4
|
+
parseNpmLockV2Project,
|
|
5
|
+
parseYarnLockV1Project,
|
|
6
|
+
parseYarnLockV2Project,
|
|
7
|
+
} from 'snyk-nodejs-lockfile-parser';
|
|
3
8
|
import type { DepGraph } from '@snyk/dep-graph';
|
|
4
9
|
import { existsSync, readFileSync } from 'node:fs';
|
|
5
10
|
import { readJsonFile } from './plugin.js';
|
|
@@ -32,12 +37,22 @@ export class DependencyContext {
|
|
|
32
37
|
pruneCycles: false,
|
|
33
38
|
});
|
|
34
39
|
} else if (lockfile === 'yarn.lock') {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
if (/#\s*yarn lockfile v1/i.test(lockfileContent)) {
|
|
41
|
+
this.depGraph = await parseYarnLockV1Project(pkgJsonContentString, lockfileContent, {
|
|
42
|
+
includeDevDeps: true,
|
|
43
|
+
includeOptionalDeps: true,
|
|
44
|
+
includePeerDeps: true,
|
|
45
|
+
strictOutOfSync: false,
|
|
46
|
+
pruneLevel: 'withinTopLevelDeps',
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
this.depGraph = await parseYarnLockV2Project(pkgJsonContentString, lockfileContent, {
|
|
50
|
+
includeDevDeps: true,
|
|
51
|
+
includeOptionalDeps: true,
|
|
52
|
+
strictOutOfSync: false,
|
|
53
|
+
pruneWithinTopLevelDeps: true,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
if (pkgJsonContent.dependencies) {
|