react19-compat-linter 1.0.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/LICENSE +21 -0
- package/README.md +55 -0
- package/bin/react19-compat-linter.js +5 -0
- package/lib/DependencyModuleListPlugin.d.ts +6 -0
- package/lib/DependencyModuleListPlugin.js +44 -0
- package/lib/cli.d.ts +1 -0
- package/lib/cli.js +42 -0
- package/lib/eslint.config.d.ts +13 -0
- package/lib/eslint.config.js +17 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +7 -0
- package/lib/no-restricted-imports.d.ts +9 -0
- package/lib/no-restricted-imports.js +158 -0
- package/lib/runLinter.d.ts +2 -0
- package/lib/runLinter.js +89 -0
- package/lib/types.d.ts +16 -0
- package/lib/types.js +2 -0
- package/lib/utils/extractPackageName.d.ts +1 -0
- package/lib/utils/extractPackageName.js +51 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Scott Mikula
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# react19-compat-linter
|
|
2
|
+
|
|
3
|
+
React 19 removes several deprecated APIs that were available in React 18 and earlier (`ReactDOM.render`, `findDOMNode`, etc.) Before upgrading to React 19, you need to remove these from your code—you might use a lint rule for this. But you also need to make sure that _all bundled dependencies_ are clean—and this problem is much harder.
|
|
4
|
+
|
|
5
|
+
This package solves this by providing a utility to scan your dependencies to identify compatibility issues. At a high level, it is composed of two parts: (1) a webpack plugin that emits a list of modules to lint, and (2) a utility that runs ESLint over those modules and reports on incompatible packages.
|
|
6
|
+
|
|
7
|
+
> **Why split the functionality like this?** While webpack is the most common bundler, you may be using something else. By decoupling the linting from the bundler, you can produce your own module list and still reuse the linting functionality. And linting is slow; you may want to separate it from your webpack process anyway.
|
|
8
|
+
|
|
9
|
+
## Getting Started
|
|
10
|
+
|
|
11
|
+
### 1. Install the package
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# NPM
|
|
15
|
+
npm install --save-dev react19-compat-linter
|
|
16
|
+
|
|
17
|
+
# Yarn
|
|
18
|
+
yarn add --dev react19-compat-linter
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Configure the webpack plugin
|
|
22
|
+
|
|
23
|
+
Add the `DependencyModuleListPlugin` to your webpack configuration to extract a list of all modules used in your build:
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
const { DependencyModuleListPlugin } = require('react19-compat-linter');
|
|
27
|
+
const outputFile = './modules-list.json';
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
// ... your webpack config
|
|
31
|
+
plugins: [
|
|
32
|
+
new DependencyModuleListPlugin(outputFile)
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
When you run webpack, this plugin will generate a JSON file containing all bundled JavaScript (or TypeScript) modules from packages in the `node_modules` directory.
|
|
38
|
+
|
|
39
|
+
### 3. Run the linter
|
|
40
|
+
|
|
41
|
+
You can run the linter via the command line:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm react19-compat-linter ./modules-list.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or integrate it into your build via the API:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const { runLinter } = require('react19-compat-linter');
|
|
51
|
+
|
|
52
|
+
runLinter('./modules-list.json').then(result => {
|
|
53
|
+
console.log(`Found violations in ${result.packages.length} packages.`);
|
|
54
|
+
});
|
|
55
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.DependencyModuleListPlugin = void 0;
|
|
13
|
+
const webpack_1 = require("webpack");
|
|
14
|
+
const promises_1 = require("fs/promises");
|
|
15
|
+
const path_1 = require("path");
|
|
16
|
+
class DependencyModuleListPlugin {
|
|
17
|
+
constructor(filePath) {
|
|
18
|
+
this.filePath = filePath;
|
|
19
|
+
}
|
|
20
|
+
apply(compiler) {
|
|
21
|
+
compiler.hooks.thisCompilation.tap('DependencyModuleListPlugin', compilation => {
|
|
22
|
+
compilation.hooks.processAssets.tapPromise({
|
|
23
|
+
name: 'DependencyModuleListPlugin',
|
|
24
|
+
stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_REPORT,
|
|
25
|
+
}, () => __awaiter(this, void 0, void 0, function* () {
|
|
26
|
+
const modules = [];
|
|
27
|
+
for (const module of compilation.modules) {
|
|
28
|
+
const resource = module.resource;
|
|
29
|
+
if (resource && shouldIncludeModule(resource)) {
|
|
30
|
+
modules.push(resource);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const jsonContent = JSON.stringify(modules, null, 2);
|
|
34
|
+
yield (0, promises_1.mkdir)((0, path_1.dirname)(this.filePath), { recursive: true });
|
|
35
|
+
yield (0, promises_1.writeFile)(this.filePath, jsonContent, 'utf-8');
|
|
36
|
+
}));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.DependencyModuleListPlugin = DependencyModuleListPlugin;
|
|
41
|
+
const jsExtensions = /\.(js|jsx|ts|tsx|mjs|cjs)$/i;
|
|
42
|
+
function shouldIncludeModule(resource) {
|
|
43
|
+
return jsExtensions.test(resource) && resource.includes('node_modules');
|
|
44
|
+
}
|
package/lib/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function cli(args: string[]): Promise<void>;
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.cli = cli;
|
|
13
|
+
const runLinter_1 = require("./runLinter");
|
|
14
|
+
function cli(args) {
|
|
15
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
if (args.length === 0) {
|
|
17
|
+
console.error('Error: Please provide the path to the modules list file');
|
|
18
|
+
console.error('Usage: react19-compat-linter <modules-list.json>');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const modulesListPath = args[0];
|
|
22
|
+
try {
|
|
23
|
+
const result = yield (0, runLinter_1.runLinter)(modulesListPath);
|
|
24
|
+
console.log(`Found violations in ${result.packages.length} packages:\n`);
|
|
25
|
+
result.packages.forEach(pkg => {
|
|
26
|
+
console.log(`${pkg.packageName} (${pkg.files.length} files):`);
|
|
27
|
+
pkg.files.forEach(file => {
|
|
28
|
+
console.log(` ${file.filePath}:`);
|
|
29
|
+
file.violations.forEach(violation => {
|
|
30
|
+
console.log(` ${violation.line}:${violation.column} - ${violation.message}`);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
console.log('');
|
|
34
|
+
});
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error('Error running linter:', error);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const no_restricted_imports_1 = require("./no-restricted-imports");
|
|
3
|
+
module.exports = [
|
|
4
|
+
{
|
|
5
|
+
plugins: {
|
|
6
|
+
'react19-compat-linter': {
|
|
7
|
+
rules: {
|
|
8
|
+
// Cast to `any` because the rule type from @typescript-eslint/utils doesn't quite match
|
|
9
|
+
'no-restricted-imports': no_restricted_imports_1.noRestrictedImports,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
rules: {
|
|
14
|
+
'react19-compat-linter/no-restricted-imports': 'error',
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
];
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runLinter = exports.DependencyModuleListPlugin = void 0;
|
|
4
|
+
var DependencyModuleListPlugin_1 = require("./DependencyModuleListPlugin");
|
|
5
|
+
Object.defineProperty(exports, "DependencyModuleListPlugin", { enumerable: true, get: function () { return DependencyModuleListPlugin_1.DependencyModuleListPlugin; } });
|
|
6
|
+
var runLinter_1 = require("./runLinter");
|
|
7
|
+
Object.defineProperty(exports, "runLinter", { enumerable: true, get: function () { return runLinter_1.runLinter; } });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const noRestrictedImports: ESLintUtils.RuleModule<"restrictedImport" | "restrictedNamespaceAccess" | "restrictedDestructuring", [{
|
|
3
|
+
restrictedImports: {
|
|
4
|
+
module: string;
|
|
5
|
+
imports: string[];
|
|
6
|
+
}[];
|
|
7
|
+
}], unknown, ESLintUtils.RuleListener> & {
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.noRestrictedImports = void 0;
|
|
4
|
+
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
+
exports.noRestrictedImports = utils_1.ESLintUtils.RuleCreator(f => f)({
|
|
6
|
+
name: 'no-restricted-imports',
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'problem',
|
|
9
|
+
messages: {
|
|
10
|
+
restrictedImport: 'Importing {{importName}} from "{{moduleName}}" is not allowed.',
|
|
11
|
+
restrictedNamespaceAccess: 'Accessing {{importName}} from {{namespace}} is not allowed.',
|
|
12
|
+
restrictedDestructuring: 'Destructuring {{importName}} from {{namespace}} is not allowed.',
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
restrictedImports: {
|
|
19
|
+
type: 'array',
|
|
20
|
+
items: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
module: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
},
|
|
26
|
+
imports: {
|
|
27
|
+
type: 'array',
|
|
28
|
+
items: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
required: ['module', 'imports'],
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
additionalProperties: false,
|
|
39
|
+
required: ['restrictedImports'],
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
docs: {
|
|
43
|
+
description: 'Restricts specific imports from specified modules.',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaultOptions: [
|
|
47
|
+
{
|
|
48
|
+
restrictedImports: [
|
|
49
|
+
{
|
|
50
|
+
module: 'react-dom',
|
|
51
|
+
imports: ['findDOMNode', 'render', 'unmountComponentAtNode'],
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
create(context, options) {
|
|
57
|
+
const restrictedImports = options[0].restrictedImports;
|
|
58
|
+
const namespaceIdentifiers = new Map();
|
|
59
|
+
return {
|
|
60
|
+
ImportDeclaration(node) {
|
|
61
|
+
const moduleName = node.source.value;
|
|
62
|
+
// Find if this module has any restricted imports
|
|
63
|
+
const restriction = restrictedImports.find(r => r.module === moduleName);
|
|
64
|
+
if (!restriction) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Check each import specifier
|
|
68
|
+
node.specifiers.forEach(specifier => {
|
|
69
|
+
if (specifier.type === 'ImportSpecifier') {
|
|
70
|
+
const importedName = specifier.imported.type === 'Identifier'
|
|
71
|
+
? specifier.imported.name
|
|
72
|
+
: specifier.imported.value;
|
|
73
|
+
if (restriction.imports.includes(importedName)) {
|
|
74
|
+
context.report({
|
|
75
|
+
node: specifier,
|
|
76
|
+
messageId: 'restrictedImport',
|
|
77
|
+
data: {
|
|
78
|
+
importName: importedName,
|
|
79
|
+
moduleName: moduleName,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (specifier.type === 'ImportDefaultSpecifier' ||
|
|
85
|
+
specifier.type === 'ImportNamespaceSpecifier') {
|
|
86
|
+
// Track namespace/default imports for this module
|
|
87
|
+
// e.g., "import React from 'react'" or "import * as React from 'react'"
|
|
88
|
+
namespaceIdentifiers.set(specifier.local.name, moduleName);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
MemberExpression(node) {
|
|
93
|
+
// Check if accessing a restricted import through a namespace identifier
|
|
94
|
+
// e.g., ReactDOM.render
|
|
95
|
+
if (node.object.type === 'Identifier' && node.property.type === 'Identifier') {
|
|
96
|
+
const namespaceName = node.object.name;
|
|
97
|
+
const propertyName = node.property.name;
|
|
98
|
+
// Check if this identifier is a tracked namespace
|
|
99
|
+
const moduleName = namespaceIdentifiers.get(namespaceName);
|
|
100
|
+
if (!moduleName) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// Find if this module has restrictions
|
|
104
|
+
const restriction = restrictedImports.find(r => r.module === moduleName);
|
|
105
|
+
if (!restriction) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Check if the accessed property is in the restricted imports list
|
|
109
|
+
if (restriction.imports.includes(propertyName)) {
|
|
110
|
+
context.report({
|
|
111
|
+
node,
|
|
112
|
+
messageId: 'restrictedNamespaceAccess',
|
|
113
|
+
data: {
|
|
114
|
+
importName: propertyName,
|
|
115
|
+
namespace: namespaceName,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
VariableDeclarator(node) {
|
|
122
|
+
// Check for destructuring from a namespace identifier
|
|
123
|
+
// e.g., const { findDOMNode } = ReactDOM;
|
|
124
|
+
if (node.id.type === 'ObjectPattern' &&
|
|
125
|
+
node.init &&
|
|
126
|
+
node.init.type === 'Identifier') {
|
|
127
|
+
const namespaceName = node.init.name;
|
|
128
|
+
// Check if this identifier is a tracked namespace
|
|
129
|
+
const moduleName = namespaceIdentifiers.get(namespaceName);
|
|
130
|
+
if (!moduleName) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Find if this module has restrictions
|
|
134
|
+
const restriction = restrictedImports.find(r => r.module === moduleName);
|
|
135
|
+
if (!restriction) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Check each destructured property
|
|
139
|
+
node.id.properties.forEach(prop => {
|
|
140
|
+
if (prop.type === 'Property' && prop.key.type === 'Identifier') {
|
|
141
|
+
const propertyName = prop.key.name;
|
|
142
|
+
if (restriction.imports.includes(propertyName)) {
|
|
143
|
+
context.report({
|
|
144
|
+
node: prop,
|
|
145
|
+
messageId: 'restrictedDestructuring',
|
|
146
|
+
data: {
|
|
147
|
+
importName: propertyName,
|
|
148
|
+
namespace: namespaceName,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
});
|
package/lib/runLinter.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.runLinter = runLinter;
|
|
46
|
+
const eslint_1 = require("eslint");
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const extractPackageName_1 = require("./utils/extractPackageName");
|
|
49
|
+
function runLinter(modulesList) {
|
|
50
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
const eslint = new eslint_1.ESLint({
|
|
52
|
+
overrideConfigFile: path.resolve(__dirname, './eslint.config.js'),
|
|
53
|
+
ignorePatterns: ['!**/node_modules/'],
|
|
54
|
+
concurrency: 'auto',
|
|
55
|
+
});
|
|
56
|
+
const allResults = yield eslint.lintFiles(modulesList);
|
|
57
|
+
// Filter for results containing the react19-compat-linter/no-restricted-imports rule violation
|
|
58
|
+
const results = allResults
|
|
59
|
+
.map(result => (Object.assign(Object.assign({}, result), { messages: result.messages.filter(msg => msg.ruleId === 'react19-compat-linter/no-restricted-imports') })))
|
|
60
|
+
.filter(result => result.messages.length > 0);
|
|
61
|
+
const files = results.map(result => ({
|
|
62
|
+
filePath: result.filePath,
|
|
63
|
+
violations: result.messages.map(msg => ({
|
|
64
|
+
line: msg.line,
|
|
65
|
+
column: msg.column,
|
|
66
|
+
message: msg.message,
|
|
67
|
+
})),
|
|
68
|
+
}));
|
|
69
|
+
// Group files by package
|
|
70
|
+
const packageMap = new Map();
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
const packageName = (0, extractPackageName_1.extractPackageName)(file.filePath);
|
|
73
|
+
if (!packageMap.has(packageName)) {
|
|
74
|
+
packageMap.set(packageName, []);
|
|
75
|
+
}
|
|
76
|
+
packageMap.get(packageName).push(file);
|
|
77
|
+
}
|
|
78
|
+
// Convert to array of package results
|
|
79
|
+
const packages = Array.from(packageMap.entries()).map(([packageName, files]) => ({
|
|
80
|
+
packageName,
|
|
81
|
+
files,
|
|
82
|
+
}));
|
|
83
|
+
// Sort packages by name for consistent output
|
|
84
|
+
packages.sort((a, b) => a.packageName.localeCompare(b.packageName));
|
|
85
|
+
return {
|
|
86
|
+
packages,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface LinterViolation {
|
|
2
|
+
line: number;
|
|
3
|
+
column: number;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface LinterFileResult {
|
|
7
|
+
filePath: string;
|
|
8
|
+
violations: LinterViolation[];
|
|
9
|
+
}
|
|
10
|
+
export interface LinterPackageResult {
|
|
11
|
+
packageName: string;
|
|
12
|
+
files: LinterFileResult[];
|
|
13
|
+
}
|
|
14
|
+
export interface LinterResult {
|
|
15
|
+
packages: LinterPackageResult[];
|
|
16
|
+
}
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function extractPackageName(filePath: string): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.extractPackageName = extractPackageName;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
function extractPackageName(filePath) {
|
|
39
|
+
const parts = filePath.split(path.sep);
|
|
40
|
+
const nodeModulesIndex = parts.lastIndexOf('node_modules');
|
|
41
|
+
if (nodeModulesIndex === -1) {
|
|
42
|
+
throw new Error(`Path does not contain node_modules: ${filePath}`);
|
|
43
|
+
}
|
|
44
|
+
// Handle scoped packages (@scope/package-name)
|
|
45
|
+
const packageParts = parts.slice(nodeModulesIndex + 1);
|
|
46
|
+
if (packageParts[0].startsWith('@')) {
|
|
47
|
+
return packageParts.slice(0, 2).join('/');
|
|
48
|
+
}
|
|
49
|
+
// Handle regular packages
|
|
50
|
+
return packageParts[0];
|
|
51
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react19-compat-linter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Utility to validate project depedencies are React 19 compatible",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"typings": "./lib/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"react19-compat-linter": "./bin/react19-compat-linter.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"prepublishOnly": "yarn test && yarn build"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/smikula/react19-compat-linter.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/smikula/react19-compat-linter/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/smikula/react19-compat-linter#readme",
|
|
23
|
+
"keywords": [
|
|
24
|
+
"react",
|
|
25
|
+
"react19",
|
|
26
|
+
"compatibility",
|
|
27
|
+
"linter",
|
|
28
|
+
"eslint",
|
|
29
|
+
"webpack",
|
|
30
|
+
"migration",
|
|
31
|
+
"deprecated",
|
|
32
|
+
"react-dom"
|
|
33
|
+
],
|
|
34
|
+
"files": [
|
|
35
|
+
"lib/",
|
|
36
|
+
"bin/",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"author": "Scott Mikula <mikula@gmail.com>",
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@typescript-eslint/utils": "^8.54.0",
|
|
46
|
+
"webpack": "^5.105.0",
|
|
47
|
+
"webpack-cli": "^6.0.1"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/eslint": "^9.6.1",
|
|
51
|
+
"@types/jest": "^30.0.0",
|
|
52
|
+
"@types/node": "^22.19.7",
|
|
53
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
54
|
+
"@typescript-eslint/rule-tester": "^8.54.0",
|
|
55
|
+
"eslint": "^9.39.2",
|
|
56
|
+
"husky": "^4.3.8",
|
|
57
|
+
"jest": "^30.2.0",
|
|
58
|
+
"prettier": "^3.8.1",
|
|
59
|
+
"pretty-quick": "^4.2.2",
|
|
60
|
+
"ts-jest": "^29.4.6",
|
|
61
|
+
"typescript": "^5.9.3"
|
|
62
|
+
},
|
|
63
|
+
"license": "MIT",
|
|
64
|
+
"husky": {
|
|
65
|
+
"hooks": {
|
|
66
|
+
"pre-commit": "pretty-quick --staged"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|