@harness-engineering/eslint-plugin 0.2.1 → 0.2.3
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 +8 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +129 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -89,6 +89,14 @@ Create `harness.config.json` in your project root:
|
|
|
89
89
|
| `no-sync-io-in-async` | Disallow synchronous I/O in async functions | warn |
|
|
90
90
|
| `no-unbounded-array-chains` | Disallow unbounded array method chains | warn |
|
|
91
91
|
|
|
92
|
+
### Cross-Platform Rules
|
|
93
|
+
|
|
94
|
+
| Rule | Description | Default |
|
|
95
|
+
| ----------------------------- | --------------------------------------------- | ------- |
|
|
96
|
+
| `no-unix-shell-command` | Disallow Unix-specific shell commands | warn |
|
|
97
|
+
| `no-hardcoded-path-separator` | Disallow hardcoded path separators | warn |
|
|
98
|
+
| `require-path-normalization` | Require path normalization for cross-platform | warn |
|
|
99
|
+
|
|
92
100
|
## Configs
|
|
93
101
|
|
|
94
102
|
- **recommended**: Architecture rules as errors, others as warnings
|
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,9 @@ declare const rules: {
|
|
|
34
34
|
'require-boundary-schema': _typescript_eslint_utils_ts_eslint.RuleModule<"missingSchema", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
35
35
|
name: string;
|
|
36
36
|
};
|
|
37
|
+
'require-path-normalization': _typescript_eslint_utils_ts_eslint.RuleModule<"missingNormalization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
declare const plugin: {
|
|
@@ -75,6 +78,9 @@ declare const plugin: {
|
|
|
75
78
|
'require-boundary-schema': _typescript_eslint_utils_ts_eslint.RuleModule<"missingSchema", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
76
79
|
name: string;
|
|
77
80
|
};
|
|
81
|
+
'require-path-normalization': _typescript_eslint_utils_ts_eslint.RuleModule<"missingNormalization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
82
|
+
name: string;
|
|
83
|
+
};
|
|
78
84
|
};
|
|
79
85
|
configs: {
|
|
80
86
|
recommended: {
|
|
@@ -89,6 +95,7 @@ declare const plugin: {
|
|
|
89
95
|
'@harness-engineering/enforce-doc-exports': string;
|
|
90
96
|
'@harness-engineering/no-unix-shell-command': string;
|
|
91
97
|
'@harness-engineering/no-hardcoded-path-separator': string;
|
|
98
|
+
'@harness-engineering/require-path-normalization': string;
|
|
92
99
|
};
|
|
93
100
|
};
|
|
94
101
|
strict: {
|
|
@@ -103,6 +110,7 @@ declare const plugin: {
|
|
|
103
110
|
'@harness-engineering/enforce-doc-exports': string;
|
|
104
111
|
'@harness-engineering/no-unix-shell-command': string;
|
|
105
112
|
'@harness-engineering/no-hardcoded-path-separator': string;
|
|
113
|
+
'@harness-engineering/require-path-normalization': string;
|
|
106
114
|
};
|
|
107
115
|
};
|
|
108
116
|
};
|
|
@@ -150,6 +158,9 @@ declare const configs: {
|
|
|
150
158
|
'require-boundary-schema': _typescript_eslint_utils_ts_eslint.RuleModule<"missingSchema", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
151
159
|
name: string;
|
|
152
160
|
};
|
|
161
|
+
'require-path-normalization': _typescript_eslint_utils_ts_eslint.RuleModule<"missingNormalization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
162
|
+
name: string;
|
|
163
|
+
};
|
|
153
164
|
};
|
|
154
165
|
configs: /*elided*/ any;
|
|
155
166
|
};
|
|
@@ -162,6 +173,7 @@ declare const configs: {
|
|
|
162
173
|
'@harness-engineering/enforce-doc-exports': string;
|
|
163
174
|
'@harness-engineering/no-unix-shell-command': string;
|
|
164
175
|
'@harness-engineering/no-hardcoded-path-separator': string;
|
|
176
|
+
'@harness-engineering/require-path-normalization': string;
|
|
165
177
|
};
|
|
166
178
|
};
|
|
167
179
|
strict: {
|
|
@@ -205,6 +217,9 @@ declare const configs: {
|
|
|
205
217
|
'require-boundary-schema': _typescript_eslint_utils_ts_eslint.RuleModule<"missingSchema", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
206
218
|
name: string;
|
|
207
219
|
};
|
|
220
|
+
'require-path-normalization': _typescript_eslint_utils_ts_eslint.RuleModule<"missingNormalization", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
|
|
221
|
+
name: string;
|
|
222
|
+
};
|
|
208
223
|
};
|
|
209
224
|
configs: /*elided*/ any;
|
|
210
225
|
};
|
|
@@ -217,6 +232,7 @@ declare const configs: {
|
|
|
217
232
|
'@harness-engineering/enforce-doc-exports': string;
|
|
218
233
|
'@harness-engineering/no-unix-shell-command': string;
|
|
219
234
|
'@harness-engineering/no-hardcoded-path-separator': string;
|
|
235
|
+
'@harness-engineering/require-path-normalization': string;
|
|
220
236
|
};
|
|
221
237
|
};
|
|
222
238
|
};
|
package/dist/index.js
CHANGED
|
@@ -23,34 +23,41 @@ function hasJSDocComment(node, sourceCode) {
|
|
|
23
23
|
}
|
|
24
24
|
return foundJSDoc;
|
|
25
25
|
}
|
|
26
|
+
var SKIP_KEYS = /* @__PURE__ */ new Set(["parent", "loc", "range", "tokens", "comments"]);
|
|
27
|
+
var ZOD_PARSE_METHODS = /* @__PURE__ */ new Set(["parse", "safeParse"]);
|
|
28
|
+
function isZodParseCall(node) {
|
|
29
|
+
if (node.type !== AST_NODE_TYPES.CallExpression) return false;
|
|
30
|
+
const callee = node.callee;
|
|
31
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) return false;
|
|
32
|
+
const prop = callee.property;
|
|
33
|
+
if (prop.type !== AST_NODE_TYPES.Identifier) return false;
|
|
34
|
+
return ZOD_PARSE_METHODS.has(prop.name);
|
|
35
|
+
}
|
|
36
|
+
function visitChildren(node, visitor) {
|
|
37
|
+
for (const key of Object.keys(node)) {
|
|
38
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
39
|
+
const value = node[key];
|
|
40
|
+
if (!value || typeof value !== "object") continue;
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
for (const item of value) {
|
|
43
|
+
if (item && typeof item === "object" && "type" in item) {
|
|
44
|
+
visitor(item);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else if ("type" in value) {
|
|
48
|
+
visitor(value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
26
52
|
function hasZodValidation(body) {
|
|
27
53
|
let found = false;
|
|
28
|
-
const skipKeys = /* @__PURE__ */ new Set(["parent", "loc", "range", "tokens", "comments"]);
|
|
29
54
|
function visit(node) {
|
|
30
55
|
if (found) return;
|
|
31
|
-
if (node
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (propType === AST_NODE_TYPES.Identifier && (prop.name === "parse" || prop.name === "safeParse")) {
|
|
35
|
-
found = true;
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
for (const key of Object.keys(node)) {
|
|
40
|
-
if (skipKeys.has(key)) continue;
|
|
41
|
-
const value = node[key];
|
|
42
|
-
if (value && typeof value === "object") {
|
|
43
|
-
if (Array.isArray(value)) {
|
|
44
|
-
for (const item of value) {
|
|
45
|
-
if (item && typeof item === "object" && "type" in item) {
|
|
46
|
-
visit(item);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
} else if ("type" in value) {
|
|
50
|
-
visit(value);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
56
|
+
if (isZodParseCall(node)) {
|
|
57
|
+
found = true;
|
|
58
|
+
return;
|
|
53
59
|
}
|
|
60
|
+
visitChildren(node, visit);
|
|
54
61
|
}
|
|
55
62
|
visit(body);
|
|
56
63
|
return found;
|
|
@@ -106,30 +113,34 @@ var enforce_doc_exports_default = createRule({
|
|
|
106
113
|
});
|
|
107
114
|
}
|
|
108
115
|
}
|
|
116
|
+
function getExportInfo(decl) {
|
|
117
|
+
const declType = decl.type;
|
|
118
|
+
if (declType === AST_NODE_TYPES2.FunctionDeclaration) {
|
|
119
|
+
const fn = decl;
|
|
120
|
+
return fn.id ? [{ kind: "function", name: fn.id.name }] : [];
|
|
121
|
+
}
|
|
122
|
+
if (declType === AST_NODE_TYPES2.ClassDeclaration) {
|
|
123
|
+
const cls = decl;
|
|
124
|
+
return cls.id ? [{ kind: "class", name: cls.id.name }] : [];
|
|
125
|
+
}
|
|
126
|
+
if (declType === AST_NODE_TYPES2.VariableDeclaration) {
|
|
127
|
+
const varDecl = decl;
|
|
128
|
+
return varDecl.declarations.filter((d) => d.id.type === AST_NODE_TYPES2.Identifier).map((d) => ({ kind: "variable", name: d.id.name }));
|
|
129
|
+
}
|
|
130
|
+
if (declType === AST_NODE_TYPES2.TSTypeAliasDeclaration && !options.ignoreTypes) {
|
|
131
|
+
return [{ kind: "type", name: decl.id.name }];
|
|
132
|
+
}
|
|
133
|
+
if (declType === AST_NODE_TYPES2.TSInterfaceDeclaration && !options.ignoreTypes) {
|
|
134
|
+
return [{ kind: "interface", name: decl.id.name }];
|
|
135
|
+
}
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
109
138
|
return {
|
|
110
139
|
ExportNamedDeclaration(node) {
|
|
111
140
|
const decl = node.declaration;
|
|
112
141
|
if (!decl) return;
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
const fn = decl;
|
|
116
|
-
if (fn.id) checkExport(node, "function", fn.id.name);
|
|
117
|
-
} else if (declType === AST_NODE_TYPES2.ClassDeclaration) {
|
|
118
|
-
const cls = decl;
|
|
119
|
-
if (cls.id) checkExport(node, "class", cls.id.name);
|
|
120
|
-
} else if (declType === AST_NODE_TYPES2.VariableDeclaration) {
|
|
121
|
-
const varDecl = decl;
|
|
122
|
-
for (const declarator of varDecl.declarations) {
|
|
123
|
-
if (declarator.id.type === AST_NODE_TYPES2.Identifier) {
|
|
124
|
-
checkExport(node, "variable", declarator.id.name);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
} else if (declType === AST_NODE_TYPES2.TSTypeAliasDeclaration && !options.ignoreTypes) {
|
|
128
|
-
const typeAlias = decl;
|
|
129
|
-
checkExport(node, "type", typeAlias.id.name);
|
|
130
|
-
} else if (declType === AST_NODE_TYPES2.TSInterfaceDeclaration && !options.ignoreTypes) {
|
|
131
|
-
const iface = decl;
|
|
132
|
-
checkExport(node, "interface", iface.id.name);
|
|
142
|
+
for (const info of getExportInfo(decl)) {
|
|
143
|
+
checkExport(node, info.kind, info.name);
|
|
133
144
|
}
|
|
134
145
|
}
|
|
135
146
|
};
|
|
@@ -819,21 +830,21 @@ function isImportOrRequire(node) {
|
|
|
819
830
|
}
|
|
820
831
|
return false;
|
|
821
832
|
}
|
|
833
|
+
function isMemberCall(callee, objectNames, methods) {
|
|
834
|
+
return callee.object.type === "Identifier" && objectNames.has(callee.object.name) && callee.property.type === "Identifier" && methods.has(callee.property.name);
|
|
835
|
+
}
|
|
836
|
+
var PATH_OBJECTS = /* @__PURE__ */ new Set(["path"]);
|
|
837
|
+
var FS_OBJECTS = /* @__PURE__ */ new Set(["fs", "fsp"]);
|
|
822
838
|
function isInFlaggedContext(node) {
|
|
823
839
|
const parent = node.parent;
|
|
824
840
|
if (!parent) return false;
|
|
825
|
-
if (parent.type
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
if (callee.type === "MemberExpression" && callee.property.type === "Identifier" && STRING_METHODS.has(callee.property.name)) {
|
|
834
|
-
return true;
|
|
835
|
-
}
|
|
836
|
-
return false;
|
|
841
|
+
if (parent.type !== "CallExpression") return false;
|
|
842
|
+
const callee = parent.callee;
|
|
843
|
+
if (callee.type !== "MemberExpression") return false;
|
|
844
|
+
if (isMemberCall(callee, PATH_OBJECTS, PATH_METHODS)) return true;
|
|
845
|
+
if (isMemberCall(callee, FS_OBJECTS, FS_METHODS)) return true;
|
|
846
|
+
if (callee.property.type === "Identifier" && STRING_METHODS.has(callee.property.name)) {
|
|
847
|
+
return true;
|
|
837
848
|
}
|
|
838
849
|
return false;
|
|
839
850
|
}
|
|
@@ -867,6 +878,63 @@ var no_hardcoded_path_separator_default = createRule10({
|
|
|
867
878
|
}
|
|
868
879
|
});
|
|
869
880
|
|
|
881
|
+
// src/rules/require-path-normalization.ts
|
|
882
|
+
import { ESLintUtils as ESLintUtils11 } from "@typescript-eslint/utils";
|
|
883
|
+
var createRule11 = ESLintUtils11.RuleCreator(
|
|
884
|
+
(name) => `https://github.com/harness-engineering/eslint-plugin/blob/main/docs/rules/${name}.md`
|
|
885
|
+
);
|
|
886
|
+
function isRelativeCall(node) {
|
|
887
|
+
const callee = node.callee;
|
|
888
|
+
if (callee.type === "Identifier" && callee.name === "relative") {
|
|
889
|
+
return true;
|
|
890
|
+
}
|
|
891
|
+
if (callee.type === "MemberExpression" && callee.object.type === "Identifier" && callee.object.name === "path" && callee.property.type === "Identifier" && callee.property.name === "relative") {
|
|
892
|
+
return true;
|
|
893
|
+
}
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
function isReplaceMethod(parent) {
|
|
897
|
+
return parent.type === "MemberExpression" && parent.property.type === "Identifier" && (parent.property.name === "replaceAll" || parent.property.name === "replace");
|
|
898
|
+
}
|
|
899
|
+
function isBackslashNormalizationArg(firstArg) {
|
|
900
|
+
if (firstArg.type === "Literal" && firstArg.value === "\\") return true;
|
|
901
|
+
if ("regex" in firstArg && firstArg.type === "Literal") return true;
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
function hasNormalization(node) {
|
|
905
|
+
const parent = node.parent;
|
|
906
|
+
if (!parent || !isReplaceMethod(parent)) return false;
|
|
907
|
+
const grandparent = parent.parent;
|
|
908
|
+
if (grandparent?.type !== "CallExpression" || grandparent.arguments.length < 1) return false;
|
|
909
|
+
return isBackslashNormalizationArg(grandparent.arguments[0]);
|
|
910
|
+
}
|
|
911
|
+
var require_path_normalization_default = createRule11({
|
|
912
|
+
name: "require-path-normalization",
|
|
913
|
+
meta: {
|
|
914
|
+
type: "problem",
|
|
915
|
+
docs: {
|
|
916
|
+
description: 'Require path.relative() results to be normalized with .replaceAll("\\\\", "/") for cross-platform safety'
|
|
917
|
+
},
|
|
918
|
+
messages: {
|
|
919
|
+
missingNormalization: "path.relative() result must be normalized with .replaceAll('\\\\', '/') for cross-platform compatibility."
|
|
920
|
+
},
|
|
921
|
+
schema: []
|
|
922
|
+
},
|
|
923
|
+
defaultOptions: [],
|
|
924
|
+
create(context) {
|
|
925
|
+
return {
|
|
926
|
+
CallExpression(node) {
|
|
927
|
+
if (!isRelativeCall(node)) return;
|
|
928
|
+
if (hasNormalization(node)) return;
|
|
929
|
+
context.report({
|
|
930
|
+
node,
|
|
931
|
+
messageId: "missingNormalization"
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
|
|
870
938
|
// src/rules/index.ts
|
|
871
939
|
var rules = {
|
|
872
940
|
"enforce-doc-exports": enforce_doc_exports_default,
|
|
@@ -878,7 +946,8 @@ var rules = {
|
|
|
878
946
|
"no-sync-io-in-async": no_sync_io_in_async_default,
|
|
879
947
|
"no-unbounded-array-chains": no_unbounded_array_chains_default,
|
|
880
948
|
"no-unix-shell-command": no_unix_shell_command_default,
|
|
881
|
-
"require-boundary-schema": require_boundary_schema_default
|
|
949
|
+
"require-boundary-schema": require_boundary_schema_default,
|
|
950
|
+
"require-path-normalization": require_path_normalization_default
|
|
882
951
|
};
|
|
883
952
|
|
|
884
953
|
// src/index.ts
|
|
@@ -902,7 +971,8 @@ var plugin = {
|
|
|
902
971
|
"@harness-engineering/require-boundary-schema": "warn",
|
|
903
972
|
"@harness-engineering/enforce-doc-exports": "warn",
|
|
904
973
|
"@harness-engineering/no-unix-shell-command": "warn",
|
|
905
|
-
"@harness-engineering/no-hardcoded-path-separator": "warn"
|
|
974
|
+
"@harness-engineering/no-hardcoded-path-separator": "warn",
|
|
975
|
+
"@harness-engineering/require-path-normalization": "warn"
|
|
906
976
|
}
|
|
907
977
|
},
|
|
908
978
|
strict: {
|
|
@@ -918,7 +988,8 @@ var plugin = {
|
|
|
918
988
|
"@harness-engineering/require-boundary-schema": "error",
|
|
919
989
|
"@harness-engineering/enforce-doc-exports": "error",
|
|
920
990
|
"@harness-engineering/no-unix-shell-command": "error",
|
|
921
|
-
"@harness-engineering/no-hardcoded-path-separator": "error"
|
|
991
|
+
"@harness-engineering/no-hardcoded-path-separator": "error",
|
|
992
|
+
"@harness-engineering/require-path-normalization": "error"
|
|
922
993
|
}
|
|
923
994
|
}
|
|
924
995
|
}
|