@harness-engineering/eslint-plugin 0.3.0 → 0.4.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/index.d.ts +24 -0
- package/dist/index.js +117 -26
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,9 @@ declare const rules: {
|
|
|
28
28
|
'no-sync-io-in-async': _typescript_eslint_utils_ts_eslint.RuleModule<"syncIoInAsync", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
29
29
|
name: string;
|
|
30
30
|
};
|
|
31
|
+
'no-undefined-optional-assignment': _typescript_eslint_utils_ts_eslint.RuleModule<"undefinedOptionalAssignment", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
32
|
+
name: string;
|
|
33
|
+
};
|
|
31
34
|
'no-unbounded-array-chains': _typescript_eslint_utils_ts_eslint.RuleModule<"unboundedArrayChain", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
32
35
|
name: string;
|
|
33
36
|
};
|
|
@@ -75,6 +78,9 @@ declare const plugin: {
|
|
|
75
78
|
'no-sync-io-in-async': _typescript_eslint_utils_ts_eslint.RuleModule<"syncIoInAsync", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
76
79
|
name: string;
|
|
77
80
|
};
|
|
81
|
+
'no-undefined-optional-assignment': _typescript_eslint_utils_ts_eslint.RuleModule<"undefinedOptionalAssignment", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
82
|
+
name: string;
|
|
83
|
+
};
|
|
78
84
|
'no-unbounded-array-chains': _typescript_eslint_utils_ts_eslint.RuleModule<"unboundedArrayChain", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
79
85
|
name: string;
|
|
80
86
|
};
|
|
@@ -99,6 +105,9 @@ declare const plugin: {
|
|
|
99
105
|
'@harness-engineering/no-forbidden-imports': string;
|
|
100
106
|
'@harness-engineering/require-boundary-schema': string;
|
|
101
107
|
'@harness-engineering/enforce-doc-exports': string;
|
|
108
|
+
'@harness-engineering/no-nested-loops-in-critical': string;
|
|
109
|
+
'@harness-engineering/no-sync-io-in-async': string;
|
|
110
|
+
'@harness-engineering/no-unbounded-array-chains': string;
|
|
102
111
|
'@harness-engineering/no-unix-shell-command': string;
|
|
103
112
|
'@harness-engineering/no-hardcoded-path-separator': string;
|
|
104
113
|
'@harness-engineering/no-process-env-in-spawn': string;
|
|
@@ -115,6 +124,9 @@ declare const plugin: {
|
|
|
115
124
|
'@harness-engineering/no-forbidden-imports': string;
|
|
116
125
|
'@harness-engineering/require-boundary-schema': string;
|
|
117
126
|
'@harness-engineering/enforce-doc-exports': string;
|
|
127
|
+
'@harness-engineering/no-nested-loops-in-critical': string;
|
|
128
|
+
'@harness-engineering/no-sync-io-in-async': string;
|
|
129
|
+
'@harness-engineering/no-unbounded-array-chains': string;
|
|
118
130
|
'@harness-engineering/no-unix-shell-command': string;
|
|
119
131
|
'@harness-engineering/no-hardcoded-path-separator': string;
|
|
120
132
|
'@harness-engineering/no-process-env-in-spawn': string;
|
|
@@ -160,6 +172,9 @@ declare const configs: {
|
|
|
160
172
|
'no-sync-io-in-async': _typescript_eslint_utils_ts_eslint.RuleModule<"syncIoInAsync", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
161
173
|
name: string;
|
|
162
174
|
};
|
|
175
|
+
'no-undefined-optional-assignment': _typescript_eslint_utils_ts_eslint.RuleModule<"undefinedOptionalAssignment", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
176
|
+
name: string;
|
|
177
|
+
};
|
|
163
178
|
'no-unbounded-array-chains': _typescript_eslint_utils_ts_eslint.RuleModule<"unboundedArrayChain", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
164
179
|
name: string;
|
|
165
180
|
};
|
|
@@ -182,6 +197,9 @@ declare const configs: {
|
|
|
182
197
|
'@harness-engineering/no-forbidden-imports': string;
|
|
183
198
|
'@harness-engineering/require-boundary-schema': string;
|
|
184
199
|
'@harness-engineering/enforce-doc-exports': string;
|
|
200
|
+
'@harness-engineering/no-nested-loops-in-critical': string;
|
|
201
|
+
'@harness-engineering/no-sync-io-in-async': string;
|
|
202
|
+
'@harness-engineering/no-unbounded-array-chains': string;
|
|
185
203
|
'@harness-engineering/no-unix-shell-command': string;
|
|
186
204
|
'@harness-engineering/no-hardcoded-path-separator': string;
|
|
187
205
|
'@harness-engineering/no-process-env-in-spawn': string;
|
|
@@ -223,6 +241,9 @@ declare const configs: {
|
|
|
223
241
|
'no-sync-io-in-async': _typescript_eslint_utils_ts_eslint.RuleModule<"syncIoInAsync", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
224
242
|
name: string;
|
|
225
243
|
};
|
|
244
|
+
'no-undefined-optional-assignment': _typescript_eslint_utils_ts_eslint.RuleModule<"undefinedOptionalAssignment", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
245
|
+
name: string;
|
|
246
|
+
};
|
|
226
247
|
'no-unbounded-array-chains': _typescript_eslint_utils_ts_eslint.RuleModule<"unboundedArrayChain", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
227
248
|
name: string;
|
|
228
249
|
};
|
|
@@ -245,6 +266,9 @@ declare const configs: {
|
|
|
245
266
|
'@harness-engineering/no-forbidden-imports': string;
|
|
246
267
|
'@harness-engineering/require-boundary-schema': string;
|
|
247
268
|
'@harness-engineering/enforce-doc-exports': string;
|
|
269
|
+
'@harness-engineering/no-nested-loops-in-critical': string;
|
|
270
|
+
'@harness-engineering/no-sync-io-in-async': string;
|
|
271
|
+
'@harness-engineering/no-unbounded-array-chains': string;
|
|
248
272
|
'@harness-engineering/no-unix-shell-command': string;
|
|
249
273
|
'@harness-engineering/no-hardcoded-path-separator': string;
|
|
250
274
|
'@harness-engineering/no-process-env-in-spawn': string;
|
package/dist/index.js
CHANGED
|
@@ -308,17 +308,27 @@ function getConfig(filePath) {
|
|
|
308
308
|
return null;
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
|
+
function getConfigRoot(filePath) {
|
|
312
|
+
const configPath = findConfigFile(path2.dirname(filePath));
|
|
313
|
+
return configPath ? path2.dirname(configPath) : null;
|
|
314
|
+
}
|
|
311
315
|
|
|
312
316
|
// src/utils/path-utils.ts
|
|
313
317
|
import * as path3 from "path";
|
|
314
318
|
import { minimatch } from "minimatch";
|
|
315
|
-
function resolveImportPath(importPath, importingFile) {
|
|
319
|
+
function resolveImportPath(importPath, importingFile, projectRoot) {
|
|
316
320
|
if (!importPath.startsWith(".")) {
|
|
317
321
|
return importPath;
|
|
318
322
|
}
|
|
319
323
|
const importingDir = path3.dirname(importingFile);
|
|
320
324
|
const resolved = path3.resolve(importingDir, importPath);
|
|
321
325
|
const normalized = resolved.replace(/\\/g, "/");
|
|
326
|
+
if (projectRoot) {
|
|
327
|
+
const root = projectRoot.replace(/\\/g, "/").replace(/\/$/, "");
|
|
328
|
+
if (normalized.startsWith(root + "/")) {
|
|
329
|
+
return normalized.slice(root.length + 1);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
322
332
|
const srcIndex = normalized.indexOf("/src/");
|
|
323
333
|
if (srcIndex !== -1) {
|
|
324
334
|
return normalized.slice(srcIndex + 1);
|
|
@@ -341,8 +351,14 @@ function getLayerForFile(filePath, layers) {
|
|
|
341
351
|
function getLayerByName(name, layers) {
|
|
342
352
|
return layers.find((l) => l.name === name);
|
|
343
353
|
}
|
|
344
|
-
function normalizePath2(filePath) {
|
|
354
|
+
function normalizePath2(filePath, projectRoot) {
|
|
345
355
|
const normalized = filePath.replace(/\\/g, "/");
|
|
356
|
+
if (projectRoot) {
|
|
357
|
+
const root = projectRoot.replace(/\\/g, "/").replace(/\/$/, "");
|
|
358
|
+
if (normalized.startsWith(root + "/")) {
|
|
359
|
+
return normalized.slice(root.length + 1);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
346
362
|
const srcIndex = normalized.indexOf("/src/");
|
|
347
363
|
if (srcIndex !== -1) {
|
|
348
364
|
return normalized.slice(srcIndex + 1);
|
|
@@ -370,9 +386,9 @@ function findViolatedRule(applicableRules, importPath, resolvedImport) {
|
|
|
370
386
|
function buildForbiddenMessage(rule, importPath) {
|
|
371
387
|
return rule.message ?? `Import "${importPath}" is forbidden in files matching "${rule.from}"`;
|
|
372
388
|
}
|
|
373
|
-
function checkImportDeclaration2(node, applicableRules, filename, report) {
|
|
389
|
+
function checkImportDeclaration2(node, applicableRules, filename, projectRoot, report) {
|
|
374
390
|
const importPath = node.source.value;
|
|
375
|
-
const resolvedImport = resolveImportPath(importPath, filename);
|
|
391
|
+
const resolvedImport = resolveImportPath(importPath, filename, projectRoot ?? void 0);
|
|
376
392
|
const violatedRule = findViolatedRule(applicableRules, importPath, resolvedImport);
|
|
377
393
|
if (violatedRule) {
|
|
378
394
|
report({
|
|
@@ -400,7 +416,8 @@ var no_forbidden_imports_default = createRule3({
|
|
|
400
416
|
if (!config?.forbiddenImports?.length) {
|
|
401
417
|
return {};
|
|
402
418
|
}
|
|
403
|
-
const
|
|
419
|
+
const projectRoot = getConfigRoot(context.filename);
|
|
420
|
+
const filePath = normalizePath2(context.filename, projectRoot ?? void 0);
|
|
404
421
|
const applicableRules = config.forbiddenImports.filter(
|
|
405
422
|
(rule) => matchesPattern(filePath, rule.from)
|
|
406
423
|
);
|
|
@@ -410,7 +427,7 @@ var no_forbidden_imports_default = createRule3({
|
|
|
410
427
|
const report = context.report.bind(context);
|
|
411
428
|
return {
|
|
412
429
|
ImportDeclaration(node) {
|
|
413
|
-
checkImportDeclaration2(node, applicableRules, context.filename, report);
|
|
430
|
+
checkImportDeclaration2(node, applicableRules, context.filename, projectRoot, report);
|
|
414
431
|
}
|
|
415
432
|
};
|
|
416
433
|
}
|
|
@@ -424,13 +441,13 @@ var createRule4 = ESLintUtils4.RuleCreator(
|
|
|
424
441
|
function isLayerViolation(importLayer, currentLayer, currentLayerDef) {
|
|
425
442
|
return importLayer !== currentLayer && !currentLayerDef.allowedDependencies.includes(importLayer);
|
|
426
443
|
}
|
|
427
|
-
function resolveImportLayer(importPath, filename, layers) {
|
|
444
|
+
function resolveImportLayer(importPath, filename, layers, projectRoot) {
|
|
428
445
|
if (!importPath.startsWith(".")) return null;
|
|
429
|
-
const resolvedImport = resolveImportPath(importPath, filename);
|
|
446
|
+
const resolvedImport = resolveImportPath(importPath, filename, projectRoot ?? void 0);
|
|
430
447
|
return getLayerForFile(resolvedImport, layers) ?? null;
|
|
431
448
|
}
|
|
432
|
-
function checkLayerImport(node, context, currentLayer, currentLayerDef, layers) {
|
|
433
|
-
const importLayer = resolveImportLayer(node.source.value, context.filename, layers);
|
|
449
|
+
function checkLayerImport(node, context, currentLayer, currentLayerDef, layers, projectRoot) {
|
|
450
|
+
const importLayer = resolveImportLayer(node.source.value, context.filename, layers, projectRoot);
|
|
434
451
|
if (!importLayer) return;
|
|
435
452
|
if (isLayerViolation(importLayer, currentLayer, currentLayerDef)) {
|
|
436
453
|
context.report({
|
|
@@ -458,7 +475,8 @@ var no_layer_violation_default = createRule4({
|
|
|
458
475
|
if (!config?.layers?.length) {
|
|
459
476
|
return {};
|
|
460
477
|
}
|
|
461
|
-
const
|
|
478
|
+
const projectRoot = getConfigRoot(context.filename);
|
|
479
|
+
const filePath = normalizePath2(context.filename, projectRoot ?? void 0);
|
|
462
480
|
const currentLayer = getLayerForFile(filePath, config.layers);
|
|
463
481
|
if (!currentLayer) {
|
|
464
482
|
return {};
|
|
@@ -470,7 +488,7 @@ var no_layer_violation_default = createRule4({
|
|
|
470
488
|
const layers = config.layers;
|
|
471
489
|
return {
|
|
472
490
|
ImportDeclaration(node) {
|
|
473
|
-
checkLayerImport(node, context, currentLayer, currentLayerDef, layers);
|
|
491
|
+
checkLayerImport(node, context, currentLayer, currentLayerDef, layers, projectRoot);
|
|
474
492
|
}
|
|
475
493
|
};
|
|
476
494
|
}
|
|
@@ -964,6 +982,14 @@ function hasProcessEnvProperty(node) {
|
|
|
964
982
|
}
|
|
965
983
|
return void 0;
|
|
966
984
|
}
|
|
985
|
+
function findEnvViolation(args) {
|
|
986
|
+
for (const arg of args) {
|
|
987
|
+
if (arg.type !== "ObjectExpression") continue;
|
|
988
|
+
const envProp = hasProcessEnvProperty(arg);
|
|
989
|
+
if (envProp) return envProp;
|
|
990
|
+
}
|
|
991
|
+
return void 0;
|
|
992
|
+
}
|
|
967
993
|
var no_process_env_in_spawn_default = createRule11({
|
|
968
994
|
name: "no-process-env-in-spawn",
|
|
969
995
|
meta: {
|
|
@@ -978,20 +1004,15 @@ var no_process_env_in_spawn_default = createRule11({
|
|
|
978
1004
|
},
|
|
979
1005
|
defaultOptions: [],
|
|
980
1006
|
create(context) {
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
const envProp = hasProcessEnvProperty(arg);
|
|
988
|
-
if (envProp) {
|
|
989
|
-
context.report({ node: envProp, messageId: "processEnvInSpawn", data: { name } });
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
}
|
|
1007
|
+
function checkCall(node) {
|
|
1008
|
+
const name = getSpawnFunctionName(node);
|
|
1009
|
+
if (!name) return;
|
|
1010
|
+
const violation = findEnvViolation(node.arguments);
|
|
1011
|
+
if (violation) {
|
|
1012
|
+
context.report({ node: violation, messageId: "processEnvInSpawn", data: { name } });
|
|
993
1013
|
}
|
|
994
|
-
}
|
|
1014
|
+
}
|
|
1015
|
+
return { CallExpression: checkCall };
|
|
995
1016
|
}
|
|
996
1017
|
});
|
|
997
1018
|
|
|
@@ -1052,6 +1073,69 @@ var require_path_normalization_default = createRule12({
|
|
|
1052
1073
|
}
|
|
1053
1074
|
});
|
|
1054
1075
|
|
|
1076
|
+
// src/rules/no-undefined-optional-assignment.ts
|
|
1077
|
+
import { ESLintUtils as ESLintUtils13 } from "@typescript-eslint/utils";
|
|
1078
|
+
var createRule13 = ESLintUtils13.RuleCreator(
|
|
1079
|
+
(name) => `https://github.com/harness-engineering/eslint-plugin/blob/main/docs/rules/${name}.md`
|
|
1080
|
+
);
|
|
1081
|
+
function unionIncludesUndefined(typeNode) {
|
|
1082
|
+
return typeNode?.type === "TSUnionType" && typeNode.types.some((t) => t.type === "TSUndefinedKeyword");
|
|
1083
|
+
}
|
|
1084
|
+
function declaredType(identifier, scope) {
|
|
1085
|
+
const ref = scope.references.find((r) => r.identifier === identifier);
|
|
1086
|
+
const name = ref?.resolved?.defs[0]?.name;
|
|
1087
|
+
if (name?.type !== "Identifier") return void 0;
|
|
1088
|
+
return name.typeAnnotation?.typeAnnotation;
|
|
1089
|
+
}
|
|
1090
|
+
function checksDefined(expr, name) {
|
|
1091
|
+
if (expr.type !== "BinaryExpression" || expr.operator !== "!==" && expr.operator !== "!=") {
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
const refsName = (n) => n.type === "Identifier" && n.name === name;
|
|
1095
|
+
const isNullish = (n) => n.type === "Identifier" && n.name === "undefined" || n.type === "Literal" && n.value === null;
|
|
1096
|
+
return refsName(expr.left) && isNullish(expr.right) || refsName(expr.right) && isNullish(expr.left);
|
|
1097
|
+
}
|
|
1098
|
+
function isGuarded(node, name) {
|
|
1099
|
+
for (let cur = node.parent; cur; cur = cur.parent) {
|
|
1100
|
+
if (cur.type === "LogicalExpression" && cur.operator === "&&" && checksDefined(cur.left, name)) {
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
var no_undefined_optional_assignment_default = createRule13({
|
|
1107
|
+
name: "no-undefined-optional-assignment",
|
|
1108
|
+
meta: {
|
|
1109
|
+
type: "suggestion",
|
|
1110
|
+
docs: {
|
|
1111
|
+
description: "Disallow assigning a possibly-undefined variable directly to an object property, which breaks `exactOptionalPropertyTypes`; use a conditional spread instead."
|
|
1112
|
+
},
|
|
1113
|
+
messages: {
|
|
1114
|
+
undefinedOptionalAssignment: "Assigning possibly-undefined '{{name}}' directly to '{{field}}' breaks exactOptionalPropertyTypes. Use a conditional spread: ...({{name}} !== undefined && {{ '{' }} {{field}}: {{name}} {{ '}' }})."
|
|
1115
|
+
},
|
|
1116
|
+
schema: []
|
|
1117
|
+
},
|
|
1118
|
+
defaultOptions: [],
|
|
1119
|
+
create(context) {
|
|
1120
|
+
return {
|
|
1121
|
+
Property(node) {
|
|
1122
|
+
if (node.computed || node.value.type !== "Identifier") return;
|
|
1123
|
+
if (node.key.type !== "Identifier" && node.key.type !== "Literal") return;
|
|
1124
|
+
const value = node.value;
|
|
1125
|
+
const scope = context.sourceCode.getScope(value);
|
|
1126
|
+
if (!unionIncludesUndefined(declaredType(value, scope))) return;
|
|
1127
|
+
if (isGuarded(node, value.name)) return;
|
|
1128
|
+
const field = node.key.type === "Identifier" ? node.key.name : String(node.key.value);
|
|
1129
|
+
context.report({
|
|
1130
|
+
node: value,
|
|
1131
|
+
messageId: "undefinedOptionalAssignment",
|
|
1132
|
+
data: { name: value.name, field }
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1055
1139
|
// src/rules/index.ts
|
|
1056
1140
|
var rules = {
|
|
1057
1141
|
"enforce-doc-exports": enforce_doc_exports_default,
|
|
@@ -1062,6 +1146,7 @@ var rules = {
|
|
|
1062
1146
|
"no-nested-loops-in-critical": no_nested_loops_in_critical_default,
|
|
1063
1147
|
"no-process-env-in-spawn": no_process_env_in_spawn_default,
|
|
1064
1148
|
"no-sync-io-in-async": no_sync_io_in_async_default,
|
|
1149
|
+
"no-undefined-optional-assignment": no_undefined_optional_assignment_default,
|
|
1065
1150
|
"no-unbounded-array-chains": no_unbounded_array_chains_default,
|
|
1066
1151
|
"no-unix-shell-command": no_unix_shell_command_default,
|
|
1067
1152
|
"require-boundary-schema": require_boundary_schema_default,
|
|
@@ -1072,7 +1157,7 @@ var rules = {
|
|
|
1072
1157
|
var plugin = {
|
|
1073
1158
|
meta: {
|
|
1074
1159
|
name: "@harness-engineering/eslint-plugin",
|
|
1075
|
-
version: "0.
|
|
1160
|
+
version: "0.3.0"
|
|
1076
1161
|
},
|
|
1077
1162
|
rules,
|
|
1078
1163
|
configs: {
|
|
@@ -1088,6 +1173,9 @@ var plugin = {
|
|
|
1088
1173
|
"@harness-engineering/no-forbidden-imports": "error",
|
|
1089
1174
|
"@harness-engineering/require-boundary-schema": "warn",
|
|
1090
1175
|
"@harness-engineering/enforce-doc-exports": "warn",
|
|
1176
|
+
"@harness-engineering/no-nested-loops-in-critical": "warn",
|
|
1177
|
+
"@harness-engineering/no-sync-io-in-async": "warn",
|
|
1178
|
+
"@harness-engineering/no-unbounded-array-chains": "warn",
|
|
1091
1179
|
"@harness-engineering/no-unix-shell-command": "warn",
|
|
1092
1180
|
"@harness-engineering/no-hardcoded-path-separator": "warn",
|
|
1093
1181
|
"@harness-engineering/no-process-env-in-spawn": "error",
|
|
@@ -1106,6 +1194,9 @@ var plugin = {
|
|
|
1106
1194
|
"@harness-engineering/no-forbidden-imports": "error",
|
|
1107
1195
|
"@harness-engineering/require-boundary-schema": "error",
|
|
1108
1196
|
"@harness-engineering/enforce-doc-exports": "error",
|
|
1197
|
+
"@harness-engineering/no-nested-loops-in-critical": "error",
|
|
1198
|
+
"@harness-engineering/no-sync-io-in-async": "error",
|
|
1199
|
+
"@harness-engineering/no-unbounded-array-chains": "error",
|
|
1109
1200
|
"@harness-engineering/no-unix-shell-command": "error",
|
|
1110
1201
|
"@harness-engineering/no-hardcoded-path-separator": "error",
|
|
1111
1202
|
"@harness-engineering/no-process-env-in-spawn": "error",
|