@lifesavertech/archguard-rules-react 2.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/README.md +9 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +412 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# @lifesavertech/archguard-rules-react
|
|
2
|
+
|
|
3
|
+
React-specific ArchGuard rules: layer import boundaries, circular dependencies, and optional UI heuristics.
|
|
4
|
+
|
|
5
|
+
UI heuristics (`noBusinessLogicInComponents`, `noDataFetchingInUI`) are **opt-in** as of v2.0. Enable them in `architecture.yaml` or use the `react-layered` init preset.
|
|
6
|
+
|
|
7
|
+
Most users should install `@lifesavertech/archguard-cli` rather than depending on this package directly.
|
|
8
|
+
|
|
9
|
+
Documentation: [github.com/lindseystead/ArchGuard](https://github.com/lindseystead/ArchGuard)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** React-specific rule implementations. */
|
|
2
|
+
import { Rule } from "@lifesavertech/archguard-core";
|
|
3
|
+
export declare function createLayerImportBoundaryRule(): Rule;
|
|
4
|
+
export declare function createBusinessLogicInComponentsRule(): Rule;
|
|
5
|
+
export declare function createDataFetchingInUIRule(): Rule;
|
|
6
|
+
export declare function createCircularDependencyRule(): Rule;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** React-specific rule implementations. */
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.createLayerImportBoundaryRule = createLayerImportBoundaryRule;
|
|
38
|
+
exports.createBusinessLogicInComponentsRule = createBusinessLogicInComponentsRule;
|
|
39
|
+
exports.createDataFetchingInUIRule = createDataFetchingInUIRule;
|
|
40
|
+
exports.createCircularDependencyRule = createCircularDependencyRule;
|
|
41
|
+
const ts = __importStar(require("typescript"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const archguard_core_1 = require("@lifesavertech/archguard-core");
|
|
44
|
+
const DATA_FETCH_HOOKS = new Set([
|
|
45
|
+
"useQuery",
|
|
46
|
+
"useSuspenseQuery",
|
|
47
|
+
"useInfiniteQuery",
|
|
48
|
+
"useMutation",
|
|
49
|
+
"useSWR",
|
|
50
|
+
"useSWRImmutable",
|
|
51
|
+
"useSWRInfinite",
|
|
52
|
+
]);
|
|
53
|
+
const DATA_FETCH_AXIOS_METHODS = new Set(["delete", "get", "patch", "post", "put", "request"]);
|
|
54
|
+
function createLayerImportBoundaryRule() {
|
|
55
|
+
return {
|
|
56
|
+
name: "layer-import-boundary",
|
|
57
|
+
description: "Enforces layer import boundaries",
|
|
58
|
+
execute(context) {
|
|
59
|
+
const violations = [];
|
|
60
|
+
if (!(0, archguard_core_1.isLayerBoundaryEnforcementEnabled)(context.architecture.rules)) {
|
|
61
|
+
return { violations: [], passed: true };
|
|
62
|
+
}
|
|
63
|
+
const filePath = context.filePath;
|
|
64
|
+
const layerName = (0, archguard_core_1.detectLayerFromPath)(filePath, context.architecture.layers);
|
|
65
|
+
if (!layerName) {
|
|
66
|
+
return { violations: [], passed: true };
|
|
67
|
+
}
|
|
68
|
+
const layerConfig = context.architecture.layers[layerName];
|
|
69
|
+
if (!layerConfig) {
|
|
70
|
+
return { violations: [], passed: true };
|
|
71
|
+
}
|
|
72
|
+
const allowedLayers = new Set(layerConfig.allowedImports);
|
|
73
|
+
for (const imp of context.imports) {
|
|
74
|
+
if (imp.isTypeOnly) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const importedLayer = detectLayerFromImport(imp, filePath, context.architecture.layers);
|
|
78
|
+
if (importedLayer && importedLayer !== layerName) {
|
|
79
|
+
if (!allowedLayers.has(importedLayer)) {
|
|
80
|
+
const sourceFile = (0, archguard_core_1.parseTypeScript)(context.sourceCode, context.filePath);
|
|
81
|
+
const importNode = findImportNode(sourceFile, imp.from);
|
|
82
|
+
if (importNode) {
|
|
83
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(importNode.getStart());
|
|
84
|
+
const allowedList = layerConfig.allowedImports.length > 0
|
|
85
|
+
? layerConfig.allowedImports.join(", ")
|
|
86
|
+
: "none";
|
|
87
|
+
violations.push({
|
|
88
|
+
file: filePath,
|
|
89
|
+
line: line + 1,
|
|
90
|
+
column: character + 1,
|
|
91
|
+
message: `Layer '${layerName}' cannot import from '${importedLayer}'. Import from allowed layers only: ${allowedList}. Move the imported code to an allowed layer or restructure dependencies.`,
|
|
92
|
+
rule: "layer-import-boundary",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
violations,
|
|
100
|
+
passed: violations.length === 0,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function createBusinessLogicInComponentsRule() {
|
|
106
|
+
return {
|
|
107
|
+
name: "no-business-logic-in-components",
|
|
108
|
+
description: "Detects business logic inside React UI layer components",
|
|
109
|
+
execute(context) {
|
|
110
|
+
const violations = [];
|
|
111
|
+
if (!context.architecture.rules.noBusinessLogicInComponents) {
|
|
112
|
+
return { violations: [], passed: true };
|
|
113
|
+
}
|
|
114
|
+
if (!(0, archguard_core_1.fileBelongsToUiLayer)(context.filePath, context.architecture)) {
|
|
115
|
+
return { violations: [], passed: true };
|
|
116
|
+
}
|
|
117
|
+
const sourceFile = (0, archguard_core_1.parseTypeScript)(context.sourceCode, context.filePath);
|
|
118
|
+
const componentNodes = getComponentNodes(sourceFile);
|
|
119
|
+
if (componentNodes.length === 0) {
|
|
120
|
+
return { violations: [], passed: true };
|
|
121
|
+
}
|
|
122
|
+
for (const componentNode of componentNodes) {
|
|
123
|
+
checkForBusinessLogic(componentNode, sourceFile, violations, context.filePath);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
violations,
|
|
127
|
+
passed: violations.length === 0,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function createDataFetchingInUIRule() {
|
|
133
|
+
return {
|
|
134
|
+
name: "no-data-fetching-in-ui",
|
|
135
|
+
description: "Prevents data fetching inside React UI layer components",
|
|
136
|
+
execute(context) {
|
|
137
|
+
const violations = [];
|
|
138
|
+
if (!context.architecture.rules.noDataFetchingInUI) {
|
|
139
|
+
return { violations: [], passed: true };
|
|
140
|
+
}
|
|
141
|
+
if (!(0, archguard_core_1.fileBelongsToUiLayer)(context.filePath, context.architecture)) {
|
|
142
|
+
return { violations: [], passed: true };
|
|
143
|
+
}
|
|
144
|
+
const sourceFile = (0, archguard_core_1.parseTypeScript)(context.sourceCode, context.filePath);
|
|
145
|
+
const componentNodes = getComponentNodes(sourceFile);
|
|
146
|
+
if (componentNodes.length === 0) {
|
|
147
|
+
return { violations: [], passed: true };
|
|
148
|
+
}
|
|
149
|
+
const importedSymbols = buildImportedSymbolLayers(context);
|
|
150
|
+
for (const componentNode of componentNodes) {
|
|
151
|
+
checkForDataFetching(componentNode, sourceFile, violations, context.filePath, importedSymbols);
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
violations,
|
|
155
|
+
passed: violations.length === 0,
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function createCircularDependencyRule() {
|
|
161
|
+
return {
|
|
162
|
+
name: "no-circular-layer-deps",
|
|
163
|
+
description: "Detects circular dependencies across layers",
|
|
164
|
+
execute(context) {
|
|
165
|
+
const violations = [];
|
|
166
|
+
if (!context.architecture.rules.noCircularLayerDeps) {
|
|
167
|
+
return { violations: [], passed: true };
|
|
168
|
+
}
|
|
169
|
+
if (!context.importGraph) {
|
|
170
|
+
return { violations: [], passed: true };
|
|
171
|
+
}
|
|
172
|
+
const cycles = detectCycles(context.importGraph);
|
|
173
|
+
for (const cycle of cycles) {
|
|
174
|
+
const sortedCycle = [...cycle].sort();
|
|
175
|
+
const firstFileInCycle = sortedCycle[0];
|
|
176
|
+
if (context.filePath === firstFileInCycle) {
|
|
177
|
+
const cyclePath = cycle
|
|
178
|
+
.map((filePath) => formatProjectRelativePath(filePath, context.projectRoot))
|
|
179
|
+
.join(" → ");
|
|
180
|
+
violations.push({
|
|
181
|
+
file: context.filePath,
|
|
182
|
+
line: 1,
|
|
183
|
+
column: 1,
|
|
184
|
+
message: `Circular dependency detected: ${cyclePath}. Break the cycle by extracting shared code to a common layer or using dependency inversion.`,
|
|
185
|
+
rule: "no-circular-layer-deps",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
violations,
|
|
191
|
+
passed: violations.length === 0,
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function detectCycles(graph) {
|
|
197
|
+
const cycles = [];
|
|
198
|
+
const visited = new Set();
|
|
199
|
+
const recStack = new Set();
|
|
200
|
+
const pathStack = [];
|
|
201
|
+
function dfs(node) {
|
|
202
|
+
visited.add(node);
|
|
203
|
+
recStack.add(node);
|
|
204
|
+
pathStack.push(node);
|
|
205
|
+
const neighbors = graph[node] || [];
|
|
206
|
+
for (const neighbor of neighbors) {
|
|
207
|
+
if (!visited.has(neighbor)) {
|
|
208
|
+
dfs(neighbor);
|
|
209
|
+
}
|
|
210
|
+
else if (recStack.has(neighbor)) {
|
|
211
|
+
const cycleStart = pathStack.indexOf(neighbor);
|
|
212
|
+
if (cycleStart !== -1) {
|
|
213
|
+
const cycle = pathStack.slice(cycleStart).concat([neighbor]);
|
|
214
|
+
cycles.push(cycle);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
recStack.delete(node);
|
|
219
|
+
pathStack.pop();
|
|
220
|
+
}
|
|
221
|
+
for (const node of Object.keys(graph)) {
|
|
222
|
+
if (!visited.has(node)) {
|
|
223
|
+
dfs(node);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return cycles;
|
|
227
|
+
}
|
|
228
|
+
function detectLayerFromImport(importedModule, fromFile, layers) {
|
|
229
|
+
if (importedModule.resolvedPath) {
|
|
230
|
+
return (0, archguard_core_1.detectLayerFromPath)(importedModule.resolvedPath, layers);
|
|
231
|
+
}
|
|
232
|
+
if (isRelativeImport(importedModule.from)) {
|
|
233
|
+
return detectLayerFromImportPath(importedModule.from, fromFile, layers);
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
function formatProjectRelativePath(filePath, projectRoot) {
|
|
238
|
+
if (!projectRoot) {
|
|
239
|
+
return filePath.replace(/\\/g, "/");
|
|
240
|
+
}
|
|
241
|
+
const relativePath = path.relative(projectRoot, filePath);
|
|
242
|
+
if (relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath)) {
|
|
243
|
+
return relativePath.replace(/\\/g, "/");
|
|
244
|
+
}
|
|
245
|
+
return filePath.replace(/\\/g, "/");
|
|
246
|
+
}
|
|
247
|
+
function detectLayerFromImportPath(importPath, fromFile, layers) {
|
|
248
|
+
const resolvedPath = path.resolve(path.dirname(fromFile), importPath);
|
|
249
|
+
return (0, archguard_core_1.detectLayerFromPath)(resolvedPath, layers);
|
|
250
|
+
}
|
|
251
|
+
function isRelativeImport(importPath) {
|
|
252
|
+
return importPath.startsWith(".") || importPath.startsWith("/");
|
|
253
|
+
}
|
|
254
|
+
function findImportNode(sourceFile, importPath) {
|
|
255
|
+
let found = null;
|
|
256
|
+
function visit(node) {
|
|
257
|
+
if (ts.isImportDeclaration(node)) {
|
|
258
|
+
const moduleSpecifier = node.moduleSpecifier;
|
|
259
|
+
if (ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === importPath) {
|
|
260
|
+
found = node;
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
ts.forEachChild(node, visit);
|
|
265
|
+
}
|
|
266
|
+
visit(sourceFile);
|
|
267
|
+
return found;
|
|
268
|
+
}
|
|
269
|
+
function getComponentNodes(sourceFile) {
|
|
270
|
+
const components = [];
|
|
271
|
+
function visit(node) {
|
|
272
|
+
if (ts.isFunctionDeclaration(node) &&
|
|
273
|
+
node.name &&
|
|
274
|
+
isPascalCase(node.name.text) &&
|
|
275
|
+
functionContainsJsx(node)) {
|
|
276
|
+
components.push(node);
|
|
277
|
+
}
|
|
278
|
+
if (ts.isVariableDeclaration(node) &&
|
|
279
|
+
ts.isIdentifier(node.name) &&
|
|
280
|
+
isPascalCase(node.name.text) &&
|
|
281
|
+
node.initializer &&
|
|
282
|
+
(ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) &&
|
|
283
|
+
functionContainsJsx(node.initializer)) {
|
|
284
|
+
components.push(node.initializer);
|
|
285
|
+
}
|
|
286
|
+
ts.forEachChild(node, visit);
|
|
287
|
+
}
|
|
288
|
+
visit(sourceFile);
|
|
289
|
+
return components;
|
|
290
|
+
}
|
|
291
|
+
function functionContainsJsx(node) {
|
|
292
|
+
if (!node.body) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
let hasJsx = false;
|
|
296
|
+
function visit(child) {
|
|
297
|
+
if (ts.isJsxElement(child) || ts.isJsxSelfClosingElement(child) || ts.isJsxFragment(child)) {
|
|
298
|
+
hasJsx = true;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
ts.forEachChild(child, visit);
|
|
302
|
+
}
|
|
303
|
+
visit(node.body);
|
|
304
|
+
return hasJsx;
|
|
305
|
+
}
|
|
306
|
+
function isPascalCase(name) {
|
|
307
|
+
return /^[A-Z]/.test(name);
|
|
308
|
+
}
|
|
309
|
+
function checkForBusinessLogic(node, sourceFile, violations, filePath) {
|
|
310
|
+
function visit(n) {
|
|
311
|
+
if (ts.isForStatement(n) ||
|
|
312
|
+
ts.isForInStatement(n) ||
|
|
313
|
+
ts.isForOfStatement(n) ||
|
|
314
|
+
ts.isWhileStatement(n) ||
|
|
315
|
+
ts.isDoStatement(n)) {
|
|
316
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(n.getStart());
|
|
317
|
+
violations.push({
|
|
318
|
+
file: filePath,
|
|
319
|
+
line: line + 1,
|
|
320
|
+
column: character + 1,
|
|
321
|
+
message: "Loop detected in UI component. Extract loop logic to a utility function in features/domain layer and pass processed data as props.",
|
|
322
|
+
rule: "no-business-logic-in-components",
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (ts.isIfStatement(n) && !isSimpleConditional(n)) {
|
|
326
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(n.getStart());
|
|
327
|
+
violations.push({
|
|
328
|
+
file: filePath,
|
|
329
|
+
line: line + 1,
|
|
330
|
+
column: character + 1,
|
|
331
|
+
message: "Complex conditional in UI component. Extract to a custom hook (e.g. useConditionalLogic) in features layer or move to domain utilities.",
|
|
332
|
+
rule: "no-business-logic-in-components",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
ts.forEachChild(n, visit);
|
|
336
|
+
}
|
|
337
|
+
visit(node);
|
|
338
|
+
}
|
|
339
|
+
function checkForDataFetching(node, sourceFile, violations, filePath, importedSymbols) {
|
|
340
|
+
function visit(n) {
|
|
341
|
+
if (ts.isCallExpression(n) && isForbiddenDataFetchCall(n, importedSymbols)) {
|
|
342
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(n.expression.getStart());
|
|
343
|
+
violations.push({
|
|
344
|
+
file: filePath,
|
|
345
|
+
line: line + 1,
|
|
346
|
+
column: character + 1,
|
|
347
|
+
message: "Data fetching in UI component violates architecture. Create a custom hook in the features layer (e.g. useUserData) and call it from the component.",
|
|
348
|
+
rule: "no-data-fetching-in-ui",
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
ts.forEachChild(n, visit);
|
|
352
|
+
}
|
|
353
|
+
visit(node);
|
|
354
|
+
}
|
|
355
|
+
function buildImportedSymbolLayers(context) {
|
|
356
|
+
const importedSymbols = new Map();
|
|
357
|
+
for (const imp of context.imports) {
|
|
358
|
+
if (imp.isTypeOnly) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const importedLayer = detectLayerFromImport(imp, context.filePath, context.architecture.layers);
|
|
362
|
+
for (const specifier of imp.specifiers) {
|
|
363
|
+
importedSymbols.set(specifier, importedLayer);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return importedSymbols;
|
|
367
|
+
}
|
|
368
|
+
function isForbiddenDataFetchCall(node, importedSymbols) {
|
|
369
|
+
if (ts.isIdentifier(node.expression)) {
|
|
370
|
+
const callee = node.expression.text;
|
|
371
|
+
if (callee === "fetch" || callee === "axios" || DATA_FETCH_HOOKS.has(callee)) {
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
return isImportedDataAccessCall(callee, importedSymbols);
|
|
375
|
+
}
|
|
376
|
+
if (ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression)) {
|
|
377
|
+
const receiver = node.expression.expression.text;
|
|
378
|
+
const method = node.expression.name.text;
|
|
379
|
+
if (receiver === "axios" && DATA_FETCH_AXIOS_METHODS.has(method)) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
if (receiver === "fetch") {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
function isImportedDataAccessCall(callee, importedSymbols) {
|
|
389
|
+
if (!importedSymbols.has(callee) || isReactHookName(callee)) {
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
const importedLayer = importedSymbols.get(callee);
|
|
393
|
+
if (!importedLayer) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
if (importedLayer === "domain") {
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
return importedLayer === "features" && isLikelyDataAccessFunctionName(callee);
|
|
400
|
+
}
|
|
401
|
+
function isReactHookName(name) {
|
|
402
|
+
return name.startsWith("use") && name.length > 3 && /[A-Z]/.test(name.charAt(3));
|
|
403
|
+
}
|
|
404
|
+
function isLikelyDataAccessFunctionName(name) {
|
|
405
|
+
return /^(fetch|load|get|read|request)/i.test(name);
|
|
406
|
+
}
|
|
407
|
+
function isSimpleConditional(node) {
|
|
408
|
+
const expression = node.expression;
|
|
409
|
+
return (ts.isIdentifier(expression) ||
|
|
410
|
+
ts.isPropertyAccessExpression(expression) ||
|
|
411
|
+
ts.isBinaryExpression(expression));
|
|
412
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lifesavertech/archguard-rules-react",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "React-specific rules for ArchGuard",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"archguard",
|
|
15
|
+
"architecture",
|
|
16
|
+
"react"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -b --force",
|
|
23
|
+
"prepack": "npm run build",
|
|
24
|
+
"build:test": "node -e \"require('fs').rmSync('dist-test',{recursive:true,force:true})\" && tsc -p tsconfig.test.json",
|
|
25
|
+
"test": "npm run build:test && node --test dist-test/index.test.js"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@lifesavertech/archguard-core": "2.0.0",
|
|
29
|
+
"typescript": "^5.0.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^20.0.0"
|
|
33
|
+
},
|
|
34
|
+
"author": "Lindsey Stead",
|
|
35
|
+
"homepage": "https://github.com/lindseystead/ArchGuard#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/lindseystead/ArchGuard/issues"
|
|
38
|
+
},
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/lindseystead/ArchGuard.git",
|
|
43
|
+
"directory": "packages/rules-react"
|
|
44
|
+
}
|
|
45
|
+
}
|