praxis-kit 0.1.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 +77 -0
- package/dist/_shared/diagnostics.d.ts +312 -0
- package/dist/_shared/diagnostics.js +360 -0
- package/dist/build-runtime-CJ_nQEaZ.js +5065 -0
- package/dist/codemod/index.d.ts +2 -0
- package/dist/codemod/index.js +176520 -0
- package/dist/contract/index.d.ts +677 -0
- package/dist/contract/index.js +341 -0
- package/dist/eslint/index.d.ts +90 -0
- package/dist/eslint/index.js +1047 -0
- package/dist/guards/index.d.ts +78 -0
- package/dist/guards/index.js +118 -0
- package/dist/html/index.d.ts +151 -0
- package/dist/html/index.js +1244 -0
- package/dist/index-BIBd_iPD.d.ts +951 -0
- package/dist/lit/index.d.ts +862 -0
- package/dist/lit/index.js +4893 -0
- package/dist/preact/index.d.ts +796 -0
- package/dist/preact/index.js +5043 -0
- package/dist/react/index.d.ts +28 -0
- package/dist/react/index.js +205 -0
- package/dist/react/legacy.d.ts +29 -0
- package/dist/react/legacy.js +80 -0
- package/dist/solid/index.d.ts +728 -0
- package/dist/solid/index.js +4821 -0
- package/dist/svelte/Polymorphic.svelte +190 -0
- package/dist/svelte/_polymorphic-runtime.d.ts +102 -0
- package/dist/svelte/_polymorphic-runtime.js +371 -0
- package/dist/svelte/index.d.ts +994 -0
- package/dist/svelte/index.js +4482 -0
- package/dist/tailwind/index.d.ts +197 -0
- package/dist/tailwind/index.js +767 -0
- package/dist/tailwind/safelist.css +20 -0
- package/dist/ts-plugin/index.cjs +166 -0
- package/dist/ts-plugin/index.d.cts +9 -0
- package/dist/utils/index.d.ts +19 -0
- package/dist/utils/index.js +21 -0
- package/dist/vite-plugin/index.d.ts +200 -0
- package/dist/vite-plugin/index.js +2106 -0
- package/dist/vue/index.d.ts +729 -0
- package/dist/vue/index.js +4945 -0
- package/dist/web/index.d.ts +832 -0
- package/dist/web/index.js +4868 -0
- package/package.json +258 -0
|
@@ -0,0 +1,1047 @@
|
|
|
1
|
+
import { RuleCreator } from "@typescript-eslint/utils/eslint-utils";
|
|
2
|
+
import "../_shared/diagnostics.js";
|
|
3
|
+
//#region ../../lib/foundation/src/iterate.ts
|
|
4
|
+
function find(iterable, callback) {
|
|
5
|
+
for (const value of iterable) {
|
|
6
|
+
const result = callback(value);
|
|
7
|
+
if (result != null) return result;
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
function some(iterable, predicate) {
|
|
12
|
+
for (const value of iterable) if (predicate(value)) return true;
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
function every(iterable, predicate) {
|
|
16
|
+
let index = 0;
|
|
17
|
+
for (const value of iterable) if (!predicate(value, index++)) return false;
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
function* filter(iterable, predicate) {
|
|
21
|
+
let index = 0;
|
|
22
|
+
for (const value of iterable) if (predicate(value, index++)) yield value;
|
|
23
|
+
}
|
|
24
|
+
function* map(iterable, callback) {
|
|
25
|
+
let index = 0;
|
|
26
|
+
for (const value of iterable) yield callback(value, index++);
|
|
27
|
+
}
|
|
28
|
+
function forEach(iterable, callback) {
|
|
29
|
+
let index = 0;
|
|
30
|
+
for (const value of iterable) callback(value, index++);
|
|
31
|
+
}
|
|
32
|
+
function reduce(iterable, initial, callback) {
|
|
33
|
+
let accumulator = initial;
|
|
34
|
+
let index = 0;
|
|
35
|
+
for (const value of iterable) accumulator = callback(accumulator, value, index++);
|
|
36
|
+
return accumulator;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Transforms an iterable into a Record.
|
|
40
|
+
*
|
|
41
|
+
* The callback returns a `[key, value]` tuple for each element. Returning
|
|
42
|
+
* `null` aborts the collection and causes `collect()` to return `null`.
|
|
43
|
+
*/
|
|
44
|
+
function collect(iterable, callback) {
|
|
45
|
+
const result = {};
|
|
46
|
+
let index = 0;
|
|
47
|
+
for (const value of iterable) {
|
|
48
|
+
const entry = callback(value, index++);
|
|
49
|
+
if (entry === null) return null;
|
|
50
|
+
result[entry[0]] = entry[1];
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
function findLast(value, callback) {
|
|
55
|
+
for (let index = value.length - 1; index >= 0; index--) {
|
|
56
|
+
const result = callback(value[index], index);
|
|
57
|
+
if (result != null) return result;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function* items(collection) {
|
|
62
|
+
for (let i = 0; i < collection.length; i++) {
|
|
63
|
+
const item = collection.item(i);
|
|
64
|
+
if (item !== null) yield item;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function nodeList(list) {
|
|
68
|
+
return { *[Symbol.iterator]() {
|
|
69
|
+
for (let i = 0; i < list.length; i++) {
|
|
70
|
+
const node = list.item(i);
|
|
71
|
+
if (node !== null) yield node;
|
|
72
|
+
}
|
|
73
|
+
} };
|
|
74
|
+
}
|
|
75
|
+
function mapEntries(m) {
|
|
76
|
+
return m.entries();
|
|
77
|
+
}
|
|
78
|
+
function set(s) {
|
|
79
|
+
return s.values();
|
|
80
|
+
}
|
|
81
|
+
function hasOwn(object, key) {
|
|
82
|
+
return Object.hasOwn(object, key);
|
|
83
|
+
}
|
|
84
|
+
function* entries(object) {
|
|
85
|
+
for (const key in object) {
|
|
86
|
+
if (!hasOwn(object, key)) continue;
|
|
87
|
+
yield [key, object[key]];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function* keys(object) {
|
|
91
|
+
for (const [key] of entries(object)) yield key;
|
|
92
|
+
}
|
|
93
|
+
function* values(object) {
|
|
94
|
+
for (const [, value] of entries(object)) yield value;
|
|
95
|
+
}
|
|
96
|
+
function mapValues(object, callback) {
|
|
97
|
+
const result = {};
|
|
98
|
+
for (const [key, value] of entries(object)) result[key] = callback(value, key);
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
function forEachEntry(object, callback) {
|
|
102
|
+
for (const [key, value] of entries(object)) callback(key, value);
|
|
103
|
+
}
|
|
104
|
+
function forEachKey(object, callback) {
|
|
105
|
+
for (const key of keys(object)) callback(key);
|
|
106
|
+
}
|
|
107
|
+
function forEachValue(object, callback) {
|
|
108
|
+
for (const value of values(object)) callback(value);
|
|
109
|
+
}
|
|
110
|
+
function forEachSet(s, callback) {
|
|
111
|
+
for (const value of s) callback(value);
|
|
112
|
+
}
|
|
113
|
+
const iterate = Object.freeze({
|
|
114
|
+
entries,
|
|
115
|
+
filter,
|
|
116
|
+
find,
|
|
117
|
+
findLast,
|
|
118
|
+
forEach,
|
|
119
|
+
forEachEntry,
|
|
120
|
+
forEachKey,
|
|
121
|
+
forEachSet,
|
|
122
|
+
forEachValue,
|
|
123
|
+
items,
|
|
124
|
+
keys,
|
|
125
|
+
map,
|
|
126
|
+
mapEntries,
|
|
127
|
+
mapValues,
|
|
128
|
+
nodeList,
|
|
129
|
+
reduce,
|
|
130
|
+
collect,
|
|
131
|
+
set,
|
|
132
|
+
some,
|
|
133
|
+
every,
|
|
134
|
+
values
|
|
135
|
+
});
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region ../../lib/foundation/src/type-guards.ts
|
|
138
|
+
function isString(value) {
|
|
139
|
+
return typeof value === "string";
|
|
140
|
+
}
|
|
141
|
+
function isObject(value, excludeArrays = false) {
|
|
142
|
+
if (value === null || typeof value !== "object") return false;
|
|
143
|
+
return excludeArrays ? !Array.isArray(value) : true;
|
|
144
|
+
}
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region ../../plugins/eslint/src/utils/ast.ts
|
|
147
|
+
/** Narrows any node-shaped value to one whose `value` property is a string — the common check
|
|
148
|
+
* behind both `asStringLiteral` and `getPropertyKey`'s `Literal` branch. */
|
|
149
|
+
function hasStringValue(node) {
|
|
150
|
+
return isObject(node, true) && isString(node.value);
|
|
151
|
+
}
|
|
152
|
+
function asObjectExpression(node) {
|
|
153
|
+
return node?.type === "ObjectExpression" ? node : void 0;
|
|
154
|
+
}
|
|
155
|
+
function asArrayExpression(node) {
|
|
156
|
+
return node?.type === "ArrayExpression" ? node : void 0;
|
|
157
|
+
}
|
|
158
|
+
function asNumericLiteral(node) {
|
|
159
|
+
if (node?.type === "Literal") {
|
|
160
|
+
const { value } = node;
|
|
161
|
+
if (typeof value === "number") return value;
|
|
162
|
+
}
|
|
163
|
+
if (node?.type === "UnaryExpression") {
|
|
164
|
+
const { operator, argument } = node;
|
|
165
|
+
if ((operator === "-" || operator === "+") && argument.type === "Literal" && typeof argument.value === "number") return operator === "-" ? -argument.value : argument.value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function asStringLiteral(node) {
|
|
169
|
+
if (node?.type === "Literal" && hasStringValue(node)) return node.value;
|
|
170
|
+
}
|
|
171
|
+
function getPropertyKey(prop) {
|
|
172
|
+
const { key } = prop;
|
|
173
|
+
if (prop.computed) return void 0;
|
|
174
|
+
if (key.type === "Identifier") return key.name;
|
|
175
|
+
if (key.type === "Literal" && hasStringValue(key)) return key.value;
|
|
176
|
+
}
|
|
177
|
+
function getObjectProperty(obj, key) {
|
|
178
|
+
return obj.properties.find((prop) => {
|
|
179
|
+
if (prop.type !== "Property" || prop.kind !== "init") return false;
|
|
180
|
+
return getPropertyKey(prop) === key;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function getFirstObjectArg(node) {
|
|
184
|
+
const [first] = node.arguments;
|
|
185
|
+
return first?.type === "ObjectExpression" ? first : void 0;
|
|
186
|
+
}
|
|
187
|
+
function isFactoryCall(node, calleeNames) {
|
|
188
|
+
const { callee } = node;
|
|
189
|
+
if (callee.type === "Identifier") return calleeNames.has(callee.name);
|
|
190
|
+
if (callee.type === "MemberExpression" && !callee.computed) {
|
|
191
|
+
const { property } = callee;
|
|
192
|
+
return property.type === "Identifier" && calleeNames.has(property.name);
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
function extractVariantValues(node) {
|
|
197
|
+
const valuesObj = asObjectExpression(node);
|
|
198
|
+
if (!valuesObj) return void 0;
|
|
199
|
+
const values = /* @__PURE__ */ new Set();
|
|
200
|
+
iterate.forEach(valuesObj.properties, (prop) => {
|
|
201
|
+
if (prop.type !== "Property") return;
|
|
202
|
+
const key = getPropertyKey(prop);
|
|
203
|
+
if (key) values.add(key);
|
|
204
|
+
});
|
|
205
|
+
return values;
|
|
206
|
+
}
|
|
207
|
+
function extractVariantMap(variantsNode) {
|
|
208
|
+
const variantsObj = asObjectExpression(variantsNode);
|
|
209
|
+
if (!variantsObj) return void 0;
|
|
210
|
+
const map = /* @__PURE__ */ new Map();
|
|
211
|
+
iterate.forEach(variantsObj.properties, (prop) => {
|
|
212
|
+
if (prop.type !== "Property") return;
|
|
213
|
+
const key = getPropertyKey(prop);
|
|
214
|
+
if (!key) return;
|
|
215
|
+
const values = extractVariantValues(prop.value);
|
|
216
|
+
if (!values) return;
|
|
217
|
+
map.set(key, values);
|
|
218
|
+
});
|
|
219
|
+
return map;
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region ../../plugins/eslint/src/diagnostics.ts
|
|
223
|
+
/**
|
|
224
|
+
* ESLint-compatible `{{ }}` template strings for meta.messages.
|
|
225
|
+
* Rules source their message text from here rather than constructing it inline.
|
|
226
|
+
*/
|
|
227
|
+
const EslintDiagnosticTemplates = {
|
|
228
|
+
redundantRole: "role=\"{{ role }}\" is redundant on <{{ tag }}>: the element already carries this implicit ARIA role. Remove the attribute.",
|
|
229
|
+
invalidChild: "<{{ child }}> is not a valid direct child of <{{ parent }}>. Allowed: {{ allowed }}.",
|
|
230
|
+
deadCompoundKey: "\"{{ key }}\" is not a variant defined in styling.variants. This compound condition can never match.",
|
|
231
|
+
deadCompoundValue: "\"{{ value }}\" is not a valid value for variant \"{{ key }}\". Expected one of: {{ allowed }}. This compound condition can never match.",
|
|
232
|
+
deadCompoundNonLiteral: "Compound value for \"{{ key }}\" is not a string literal and cannot be statically validated.",
|
|
233
|
+
missingStrict: "enforcement.{{ field }} is defined but enforcement.diagnostics is not explicitly set. Pass a Diagnostics instance (e.g. warnDiagnostics, throwDiagnostics) so the enforcement behavior is clear at the call site.",
|
|
234
|
+
invalidDefaultKey: "\"{{ key }}\" is not a variant defined in styling.variants. This default will have no effect.",
|
|
235
|
+
invalidDefaultValue: "\"{{ value }}\" is not a valid value for variant \"{{ key }}\". Expected one of: {{ allowed }}. This default will have no effect.",
|
|
236
|
+
invalidDefaultNonLiteral: "Default value for \"{{ key }}\" is not a string literal and cannot be statically validated.",
|
|
237
|
+
negativeMin: "cardinality.min must be >= 0 (got {{ value }}).",
|
|
238
|
+
negativeMax: "cardinality.max must be >= 0 (got {{ value }}).",
|
|
239
|
+
maxLessThanMin: "cardinality.max ({{ max }}) must be >= cardinality.min ({{ min }}). This rule can never be satisfied.",
|
|
240
|
+
multipleFirst: "Multiple enforcement.children rules require position: \"first\". Only one child can occupy the first position.",
|
|
241
|
+
multipleLast: "Multiple enforcement.children rules require position: \"last\". Only one child can occupy the last position.",
|
|
242
|
+
minSumExceedsCapacity: "A rule with position: \"only\" requires min >= 1, but {{ count }} other rule(s) also require min >= 1. These constraints cannot be satisfied simultaneously."
|
|
243
|
+
};
|
|
244
|
+
const noDeadCompound = RuleCreator((name) => `https://praxis-kit.dev/eslint-rules/${name}`)({
|
|
245
|
+
name: "no-dead-compound",
|
|
246
|
+
meta: {
|
|
247
|
+
type: "problem",
|
|
248
|
+
docs: { description: "Disallow compound variant conditions that reference unknown variant keys or values — compounds that can never fire." },
|
|
249
|
+
messages: {
|
|
250
|
+
unknownVariantKey: EslintDiagnosticTemplates.deadCompoundKey,
|
|
251
|
+
unknownVariantValue: EslintDiagnosticTemplates.deadCompoundValue,
|
|
252
|
+
nonLiteralValue: EslintDiagnosticTemplates.deadCompoundNonLiteral
|
|
253
|
+
},
|
|
254
|
+
schema: [{
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: {
|
|
257
|
+
calleeNames: {
|
|
258
|
+
type: "array",
|
|
259
|
+
items: { type: "string" }
|
|
260
|
+
},
|
|
261
|
+
reportNonLiteral: { type: "boolean" }
|
|
262
|
+
},
|
|
263
|
+
additionalProperties: false
|
|
264
|
+
}]
|
|
265
|
+
},
|
|
266
|
+
defaultOptions: [{}],
|
|
267
|
+
create(context) {
|
|
268
|
+
const calleeNames = new Set(context.options[0]?.calleeNames ?? ["createContractComponent"]);
|
|
269
|
+
const reportNonLiteral = context.options[0]?.reportNonLiteral ?? false;
|
|
270
|
+
return { CallExpression(node) {
|
|
271
|
+
if (!isFactoryCall(node, calleeNames)) return;
|
|
272
|
+
const arg = getFirstObjectArg(node);
|
|
273
|
+
if (!arg) return;
|
|
274
|
+
const stylingProp = getObjectProperty(arg, "styling");
|
|
275
|
+
if (!stylingProp) return;
|
|
276
|
+
const styling = asObjectExpression(stylingProp.value);
|
|
277
|
+
if (!styling) return;
|
|
278
|
+
const variantsProp = getObjectProperty(styling, "variants");
|
|
279
|
+
if (!variantsProp) return;
|
|
280
|
+
const variantMap = extractVariantMap(variantsProp.value);
|
|
281
|
+
if (!variantMap || variantMap.size === 0) return;
|
|
282
|
+
const compoundsProp = getObjectProperty(styling, "compounds");
|
|
283
|
+
if (!compoundsProp) return;
|
|
284
|
+
const compounds = asArrayExpression(compoundsProp.value);
|
|
285
|
+
if (!compounds) return;
|
|
286
|
+
iterate.forEach(compounds.elements, (element) => {
|
|
287
|
+
if (!element || element.type !== "ObjectExpression") return;
|
|
288
|
+
iterate.forEach(element.properties, (prop) => {
|
|
289
|
+
if (prop.type !== "Property") return;
|
|
290
|
+
const literalProp = prop;
|
|
291
|
+
const key = getPropertyKey(literalProp);
|
|
292
|
+
if (!key || key === "class" || key === "className") return;
|
|
293
|
+
if (!variantMap.has(key)) {
|
|
294
|
+
context.report({
|
|
295
|
+
node: prop,
|
|
296
|
+
messageId: "unknownVariantKey",
|
|
297
|
+
data: { key }
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const value = asStringLiteral(literalProp.value);
|
|
302
|
+
if (value === void 0) {
|
|
303
|
+
if (reportNonLiteral) context.report({
|
|
304
|
+
node: prop,
|
|
305
|
+
messageId: "nonLiteralValue",
|
|
306
|
+
data: { key }
|
|
307
|
+
});
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const allowed = variantMap.get(key);
|
|
311
|
+
if (!allowed.has(value)) context.report({
|
|
312
|
+
node: prop,
|
|
313
|
+
messageId: "unknownVariantValue",
|
|
314
|
+
data: {
|
|
315
|
+
key,
|
|
316
|
+
value,
|
|
317
|
+
allowed: iterate.reduce([...allowed], "", (acc, value, index) => index === 0 ? `"${value}"` : `${acc}, "${value}"`)
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
} };
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
const noEnforcementWithoutStrict = RuleCreator((name) => `https://praxis-kit.dev/eslint-rules/${name}`)({
|
|
326
|
+
name: "no-enforcement-without-strict",
|
|
327
|
+
meta: {
|
|
328
|
+
type: "problem",
|
|
329
|
+
docs: { description: "Require enforcement.strict when enforcement.children or enforcement.aria is defined." },
|
|
330
|
+
messages: { missingStrict: EslintDiagnosticTemplates.missingStrict },
|
|
331
|
+
schema: [{
|
|
332
|
+
type: "object",
|
|
333
|
+
properties: { calleeNames: {
|
|
334
|
+
type: "array",
|
|
335
|
+
items: { type: "string" }
|
|
336
|
+
} },
|
|
337
|
+
additionalProperties: false
|
|
338
|
+
}]
|
|
339
|
+
},
|
|
340
|
+
defaultOptions: [{}],
|
|
341
|
+
create(context) {
|
|
342
|
+
const calleeNames = new Set(context.options[0]?.calleeNames ?? ["createContractComponent"]);
|
|
343
|
+
return { CallExpression(node) {
|
|
344
|
+
if (!isFactoryCall(node, calleeNames)) return;
|
|
345
|
+
const arg = getFirstObjectArg(node);
|
|
346
|
+
if (!arg) return;
|
|
347
|
+
const enfProp = getObjectProperty(arg, "enforcement");
|
|
348
|
+
if (!enfProp) return;
|
|
349
|
+
const enf = asObjectExpression(enfProp.value);
|
|
350
|
+
if (!enf) return;
|
|
351
|
+
if (getObjectProperty(enf, "diagnostics") !== void 0) return;
|
|
352
|
+
const field = iterate.find(["children", "aria"], (field) => {
|
|
353
|
+
const fieldProp = getObjectProperty(enf, field);
|
|
354
|
+
if (!fieldProp) return null;
|
|
355
|
+
if (field === "children") {
|
|
356
|
+
const arr = asArrayExpression(fieldProp.value);
|
|
357
|
+
if (!arr || arr.elements.length === 0) return null;
|
|
358
|
+
}
|
|
359
|
+
return field;
|
|
360
|
+
});
|
|
361
|
+
if (field) context.report({
|
|
362
|
+
node,
|
|
363
|
+
messageId: "missingStrict",
|
|
364
|
+
data: { field }
|
|
365
|
+
});
|
|
366
|
+
} };
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
//#endregion
|
|
370
|
+
//#region ../../plugins/eslint/src/rules/no-invalid-default.ts
|
|
371
|
+
function formatAllowedValues(values) {
|
|
372
|
+
return Array.from(values, (value) => `"${value}"`).join(", ");
|
|
373
|
+
}
|
|
374
|
+
const noInvalidDefault = RuleCreator((name) => `https://praxis-kit.dev/eslint-rules/${name}`)({
|
|
375
|
+
name: "no-invalid-default",
|
|
376
|
+
meta: {
|
|
377
|
+
type: "problem",
|
|
378
|
+
docs: { description: "Disallow styling.defaults entries whose keys or values do not exist in styling.variants." },
|
|
379
|
+
messages: {
|
|
380
|
+
unknownDefaultKey: EslintDiagnosticTemplates.invalidDefaultKey,
|
|
381
|
+
unknownDefaultValue: EslintDiagnosticTemplates.invalidDefaultValue,
|
|
382
|
+
nonLiteralValue: EslintDiagnosticTemplates.invalidDefaultNonLiteral
|
|
383
|
+
},
|
|
384
|
+
schema: [{
|
|
385
|
+
type: "object",
|
|
386
|
+
properties: {
|
|
387
|
+
calleeNames: {
|
|
388
|
+
type: "array",
|
|
389
|
+
items: { type: "string" }
|
|
390
|
+
},
|
|
391
|
+
reportNonLiteral: { type: "boolean" }
|
|
392
|
+
},
|
|
393
|
+
additionalProperties: false
|
|
394
|
+
}]
|
|
395
|
+
},
|
|
396
|
+
defaultOptions: [{}],
|
|
397
|
+
create(context) {
|
|
398
|
+
const calleeNames = new Set(context.options[0]?.calleeNames ?? ["createContractComponent"]);
|
|
399
|
+
const reportNonLiteral = context.options[0]?.reportNonLiteral ?? false;
|
|
400
|
+
return { CallExpression(node) {
|
|
401
|
+
if (!isFactoryCall(node, calleeNames)) return;
|
|
402
|
+
const arg = getFirstObjectArg(node);
|
|
403
|
+
if (!arg) return;
|
|
404
|
+
const stylingProp = getObjectProperty(arg, "styling");
|
|
405
|
+
if (!stylingProp) return;
|
|
406
|
+
const styling = asObjectExpression(stylingProp.value);
|
|
407
|
+
if (!styling) return;
|
|
408
|
+
const variantsProp = getObjectProperty(styling, "variants");
|
|
409
|
+
if (!variantsProp) return;
|
|
410
|
+
const variantMap = extractVariantMap(variantsProp.value);
|
|
411
|
+
if (!variantMap || variantMap.size === 0) return;
|
|
412
|
+
const defaultsProp = getObjectProperty(styling, "defaults");
|
|
413
|
+
if (!defaultsProp) return;
|
|
414
|
+
const defaults = asObjectExpression(defaultsProp.value);
|
|
415
|
+
if (!defaults) return;
|
|
416
|
+
iterate.forEach(defaults.properties, (prop) => {
|
|
417
|
+
if (prop.type !== "Property") return;
|
|
418
|
+
const key = getPropertyKey(prop);
|
|
419
|
+
if (!key) return;
|
|
420
|
+
if (!variantMap.has(key)) {
|
|
421
|
+
context.report({
|
|
422
|
+
node: prop,
|
|
423
|
+
messageId: "unknownDefaultKey",
|
|
424
|
+
data: { key }
|
|
425
|
+
});
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const value = asStringLiteral(prop.value);
|
|
429
|
+
if (value === void 0) {
|
|
430
|
+
if (reportNonLiteral) context.report({
|
|
431
|
+
node: prop,
|
|
432
|
+
messageId: "nonLiteralValue",
|
|
433
|
+
data: { key }
|
|
434
|
+
});
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
const allowed = variantMap.get(key);
|
|
438
|
+
if (!allowed.has(value)) context.report({
|
|
439
|
+
node: prop,
|
|
440
|
+
messageId: "unknownDefaultValue",
|
|
441
|
+
data: {
|
|
442
|
+
key,
|
|
443
|
+
value,
|
|
444
|
+
allowed: formatAllowedValues(allowed)
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
} };
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region ../../plugins/eslint/src/utils/content-model-builders.ts
|
|
453
|
+
function categories(...values) {
|
|
454
|
+
return new Set(values);
|
|
455
|
+
}
|
|
456
|
+
function tags(...values) {
|
|
457
|
+
return new Set(values);
|
|
458
|
+
}
|
|
459
|
+
function category(...allowed) {
|
|
460
|
+
return { model: {
|
|
461
|
+
kind: "category",
|
|
462
|
+
allowed: categories(...allowed)
|
|
463
|
+
} };
|
|
464
|
+
}
|
|
465
|
+
function specific(...allowed) {
|
|
466
|
+
return { model: {
|
|
467
|
+
kind: "specific",
|
|
468
|
+
allowed: tags(...allowed)
|
|
469
|
+
} };
|
|
470
|
+
}
|
|
471
|
+
function phrasing() {
|
|
472
|
+
return category("phrasing");
|
|
473
|
+
}
|
|
474
|
+
function phrasingOrHeading() {
|
|
475
|
+
return category("phrasing", "heading");
|
|
476
|
+
}
|
|
477
|
+
/** Builds a `{ tag: categorySet }` slice for `TAG_CATEGORIES`, one category set shared across
|
|
478
|
+
* every tag in `tagNames` — the common case where a whole group of tags shares identical
|
|
479
|
+
* category membership (e.g. every plain flow+phrasing inline element). Spread the result into
|
|
480
|
+
* `TAG_CATEGORIES`'s object literal rather than mutating an already-built map — each tag's
|
|
481
|
+
* entry is then created exactly once, with no runtime mutation after construction. */
|
|
482
|
+
function categoriesFor(categoryList, tagNames) {
|
|
483
|
+
return Object.fromEntries(tagNames.map((tagName) => [tagName, categories(...categoryList)]));
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Identity function over a content-model table literal — exists purely so a `HTML_CONTENT_MODELS`
|
|
487
|
+
* table can be authored as a plain object literal (readable, one entry per line) while still
|
|
488
|
+
* getting `const`-context literal inference for each entry's `kind` discriminant, the same
|
|
489
|
+
* benefit an explicit `ContentModelMap` annotation would otherwise cost by widening every
|
|
490
|
+
* property to the annotation's type up front. TypeScript's `const` type parameter modifier
|
|
491
|
+
* (5.0+) does the actual work; this function just gives it a name at the call site.
|
|
492
|
+
*/
|
|
493
|
+
function defineContentModels(definitions) {
|
|
494
|
+
return definitions;
|
|
495
|
+
}
|
|
496
|
+
//#endregion
|
|
497
|
+
//#region ../../plugins/eslint/src/utils/html-nesting.ts
|
|
498
|
+
const FLOW_PHRASING_TAGS = [
|
|
499
|
+
"abbr",
|
|
500
|
+
"b",
|
|
501
|
+
"bdi",
|
|
502
|
+
"bdo",
|
|
503
|
+
"br",
|
|
504
|
+
"cite",
|
|
505
|
+
"code",
|
|
506
|
+
"data",
|
|
507
|
+
"dfn",
|
|
508
|
+
"em",
|
|
509
|
+
"i",
|
|
510
|
+
"kbd",
|
|
511
|
+
"mark",
|
|
512
|
+
"output",
|
|
513
|
+
"q",
|
|
514
|
+
"ruby",
|
|
515
|
+
"s",
|
|
516
|
+
"samp",
|
|
517
|
+
"small",
|
|
518
|
+
"span",
|
|
519
|
+
"strong",
|
|
520
|
+
"sub",
|
|
521
|
+
"sup",
|
|
522
|
+
"time",
|
|
523
|
+
"u",
|
|
524
|
+
"var",
|
|
525
|
+
"wbr"
|
|
526
|
+
];
|
|
527
|
+
const FLOW_PHRASING_EMBEDDED_TAGS = [
|
|
528
|
+
"audio",
|
|
529
|
+
"canvas",
|
|
530
|
+
"img",
|
|
531
|
+
"math",
|
|
532
|
+
"object",
|
|
533
|
+
"picture",
|
|
534
|
+
"svg",
|
|
535
|
+
"video"
|
|
536
|
+
];
|
|
537
|
+
const FLOW_PHRASING_INTERACTIVE_TAGS = [
|
|
538
|
+
"button",
|
|
539
|
+
"input",
|
|
540
|
+
"label",
|
|
541
|
+
"select",
|
|
542
|
+
"textarea"
|
|
543
|
+
];
|
|
544
|
+
const FLOW_PHRASING_EMBEDDED_INTERACTIVE_TAGS = ["embed", "iframe"];
|
|
545
|
+
const FLOW_PHRASING_METADATA_TAGS = [
|
|
546
|
+
"meta",
|
|
547
|
+
"noscript",
|
|
548
|
+
"script",
|
|
549
|
+
"template",
|
|
550
|
+
"link"
|
|
551
|
+
];
|
|
552
|
+
const FLOW_ONLY_TAGS = [
|
|
553
|
+
"address",
|
|
554
|
+
"blockquote",
|
|
555
|
+
"dialog",
|
|
556
|
+
"div",
|
|
557
|
+
"dl",
|
|
558
|
+
"fieldset",
|
|
559
|
+
"figure",
|
|
560
|
+
"footer",
|
|
561
|
+
"form",
|
|
562
|
+
"header",
|
|
563
|
+
"hr",
|
|
564
|
+
"li",
|
|
565
|
+
"main",
|
|
566
|
+
"menu",
|
|
567
|
+
"ol",
|
|
568
|
+
"p",
|
|
569
|
+
"pre",
|
|
570
|
+
"summary",
|
|
571
|
+
"table",
|
|
572
|
+
"ul"
|
|
573
|
+
];
|
|
574
|
+
const FLOW_SECTIONING_TAGS = [
|
|
575
|
+
"article",
|
|
576
|
+
"aside",
|
|
577
|
+
"nav",
|
|
578
|
+
"section"
|
|
579
|
+
];
|
|
580
|
+
const FLOW_INTERACTIVE_TAGS = ["details"];
|
|
581
|
+
const FLOW_METADATA_TAGS = ["style"];
|
|
582
|
+
const FLOW_HEADING_TAGS = [
|
|
583
|
+
"h1",
|
|
584
|
+
"h2",
|
|
585
|
+
"h3",
|
|
586
|
+
"h4",
|
|
587
|
+
"h5",
|
|
588
|
+
"h6",
|
|
589
|
+
"hgroup"
|
|
590
|
+
];
|
|
591
|
+
const METADATA_ONLY_TAGS = ["base", "title"];
|
|
592
|
+
const TAG_CATEGORIES = {
|
|
593
|
+
...categoriesFor(["flow", "phrasing"], FLOW_PHRASING_TAGS),
|
|
594
|
+
...categoriesFor([
|
|
595
|
+
"flow",
|
|
596
|
+
"phrasing",
|
|
597
|
+
"embedded"
|
|
598
|
+
], FLOW_PHRASING_EMBEDDED_TAGS),
|
|
599
|
+
...categoriesFor([
|
|
600
|
+
"flow",
|
|
601
|
+
"phrasing",
|
|
602
|
+
"interactive"
|
|
603
|
+
], FLOW_PHRASING_INTERACTIVE_TAGS),
|
|
604
|
+
...categoriesFor([
|
|
605
|
+
"flow",
|
|
606
|
+
"phrasing",
|
|
607
|
+
"embedded",
|
|
608
|
+
"interactive"
|
|
609
|
+
], FLOW_PHRASING_EMBEDDED_INTERACTIVE_TAGS),
|
|
610
|
+
...categoriesFor([
|
|
611
|
+
"metadata",
|
|
612
|
+
"flow",
|
|
613
|
+
"phrasing"
|
|
614
|
+
], FLOW_PHRASING_METADATA_TAGS),
|
|
615
|
+
...categoriesFor(["flow"], FLOW_ONLY_TAGS),
|
|
616
|
+
...categoriesFor(["flow", "sectioning"], FLOW_SECTIONING_TAGS),
|
|
617
|
+
...categoriesFor(["flow", "interactive"], FLOW_INTERACTIVE_TAGS),
|
|
618
|
+
...categoriesFor(["flow", "metadata"], FLOW_METADATA_TAGS),
|
|
619
|
+
...categoriesFor(["flow", "heading"], FLOW_HEADING_TAGS),
|
|
620
|
+
...categoriesFor(["metadata"], METADATA_ONLY_TAGS)
|
|
621
|
+
};
|
|
622
|
+
const HTML_CONTENT_MODELS = defineContentModels({
|
|
623
|
+
colgroup: specific("col", "template"),
|
|
624
|
+
dl: specific("dt", "dd", "div", "script", "template"),
|
|
625
|
+
menu: specific("li", "script", "template"),
|
|
626
|
+
ol: specific("li", "script", "template"),
|
|
627
|
+
optgroup: specific("option", "script", "template"),
|
|
628
|
+
picture: specific("source", "img", "script", "template"),
|
|
629
|
+
select: specific("option", "optgroup", "hr", "script", "template"),
|
|
630
|
+
table: specific("caption", "colgroup", "thead", "tbody", "tfoot", "tr", "script", "template"),
|
|
631
|
+
tbody: specific("tr", "script", "template"),
|
|
632
|
+
tfoot: specific("tr", "script", "template"),
|
|
633
|
+
thead: specific("tr", "script", "template"),
|
|
634
|
+
tr: specific("td", "th", "script", "template"),
|
|
635
|
+
ul: specific("li", "script", "template"),
|
|
636
|
+
abbr: phrasing(),
|
|
637
|
+
b: phrasing(),
|
|
638
|
+
bdi: phrasing(),
|
|
639
|
+
bdo: phrasing(),
|
|
640
|
+
cite: phrasing(),
|
|
641
|
+
code: phrasing(),
|
|
642
|
+
data: phrasing(),
|
|
643
|
+
dfn: phrasing(),
|
|
644
|
+
dt: phrasing(),
|
|
645
|
+
em: phrasing(),
|
|
646
|
+
h1: phrasing(),
|
|
647
|
+
h2: phrasing(),
|
|
648
|
+
h3: phrasing(),
|
|
649
|
+
h4: phrasing(),
|
|
650
|
+
h5: phrasing(),
|
|
651
|
+
h6: phrasing(),
|
|
652
|
+
i: phrasing(),
|
|
653
|
+
kbd: phrasing(),
|
|
654
|
+
label: phrasing(),
|
|
655
|
+
mark: phrasing(),
|
|
656
|
+
output: phrasing(),
|
|
657
|
+
p: phrasing(),
|
|
658
|
+
q: phrasing(),
|
|
659
|
+
ruby: phrasing(),
|
|
660
|
+
s: phrasing(),
|
|
661
|
+
samp: phrasing(),
|
|
662
|
+
small: phrasing(),
|
|
663
|
+
span: phrasing(),
|
|
664
|
+
strong: phrasing(),
|
|
665
|
+
sub: phrasing(),
|
|
666
|
+
sup: phrasing(),
|
|
667
|
+
time: phrasing(),
|
|
668
|
+
u: phrasing(),
|
|
669
|
+
var: phrasing(),
|
|
670
|
+
legend: phrasingOrHeading(),
|
|
671
|
+
summary: phrasingOrHeading()
|
|
672
|
+
});
|
|
673
|
+
function getTagCategorySet(tagCategories, tagName) {
|
|
674
|
+
return tagCategories[tagName];
|
|
675
|
+
}
|
|
676
|
+
function getContentModelDefinition(contentModels, tagName) {
|
|
677
|
+
return contentModels[tagName];
|
|
678
|
+
}
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region ../../plugins/eslint/src/rules/no-invalid-html-nesting.ts
|
|
681
|
+
const createRule$3 = RuleCreator((name) => `https://praxis-kit.dev/eslint-rules/${name}`);
|
|
682
|
+
function describeAllowed(definition) {
|
|
683
|
+
const { model } = definition;
|
|
684
|
+
switch (model.kind) {
|
|
685
|
+
case "specific": return [...model.allowed].join(", ");
|
|
686
|
+
case "category": return [...model.allowed].map((c) => `${c} content`).join(", ");
|
|
687
|
+
case "transparent":
|
|
688
|
+
case "nothing":
|
|
689
|
+
case "structured": return "";
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const ALLOWED_TEXT = Object.fromEntries(Object.entries(HTML_CONTENT_MODELS).filter((entry) => entry[1] !== void 0).map(([tag, definition]) => [tag, describeAllowed(definition)]));
|
|
693
|
+
function getIntrinsicTag(name) {
|
|
694
|
+
if (name.type !== "JSXIdentifier") return void 0;
|
|
695
|
+
return /^[a-z]/.test(name.name) ? name.name : void 0;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Evaluates a `ContentModelDefinition` against a candidate child tag.
|
|
699
|
+
*
|
|
700
|
+
* Only `specific` (explicit tag allowlist) and `category` (content-category membership) are
|
|
701
|
+
* evaluated today — the same two kinds every entry in `HTML_CONTENT_MODELS` currently uses.
|
|
702
|
+
* `transparent`/`nothing`/`structured` are real HTML content-model concepts (see their own doc
|
|
703
|
+
* comments in `types/content-model.ts`) that this validator doesn't check yet: `transparent`
|
|
704
|
+
* needs the parent's own rendering context, `nothing` needs a void-element check this rule
|
|
705
|
+
* doesn't perform, and `structured` needs an order-aware grammar evaluator, not a flat
|
|
706
|
+
* membership test. Each passes through (no violation reported) rather than being treated as
|
|
707
|
+
* disallowing everything, so introducing one of these kinds to a future table entry doesn't
|
|
708
|
+
* silently start flagging valid markup the validator can't actually reason about yet.
|
|
709
|
+
*/
|
|
710
|
+
function isAllowed(childTag, definition) {
|
|
711
|
+
const { model } = definition;
|
|
712
|
+
switch (model.kind) {
|
|
713
|
+
case "specific": return model.allowed.has(childTag);
|
|
714
|
+
case "category": {
|
|
715
|
+
const cats = getTagCategorySet(TAG_CATEGORIES, childTag);
|
|
716
|
+
if (!cats) return true;
|
|
717
|
+
return [...model.allowed].some((c) => cats.has(c));
|
|
718
|
+
}
|
|
719
|
+
case "transparent":
|
|
720
|
+
case "nothing":
|
|
721
|
+
case "structured": return true;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const noInvalidHtmlNesting = createRule$3({
|
|
725
|
+
name: "no-invalid-html-nesting",
|
|
726
|
+
meta: {
|
|
727
|
+
type: "problem",
|
|
728
|
+
docs: { description: "Disallow HTML children that violate the HTML5 content model for their parent element." },
|
|
729
|
+
messages: { invalidChild: EslintDiagnosticTemplates.invalidChild },
|
|
730
|
+
schema: []
|
|
731
|
+
},
|
|
732
|
+
defaultOptions: [],
|
|
733
|
+
create(context) {
|
|
734
|
+
return { JSXElement(node) {
|
|
735
|
+
const parentTag = getIntrinsicTag(node.openingElement.name);
|
|
736
|
+
if (!parentTag) return;
|
|
737
|
+
const definition = getContentModelDefinition(HTML_CONTENT_MODELS, parentTag);
|
|
738
|
+
if (!definition) return;
|
|
739
|
+
iterate.forEach(node.children, (child) => {
|
|
740
|
+
if (child.type !== "JSXElement") return;
|
|
741
|
+
const childTag = getIntrinsicTag(child.openingElement.name);
|
|
742
|
+
if (childTag === void 0) return;
|
|
743
|
+
if (isAllowed(childTag, definition)) return;
|
|
744
|
+
context.report({
|
|
745
|
+
node: child,
|
|
746
|
+
messageId: "invalidChild",
|
|
747
|
+
data: {
|
|
748
|
+
child: childTag,
|
|
749
|
+
parent: parentTag,
|
|
750
|
+
allowed: ALLOWED_TEXT[parentTag] ?? ""
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
} };
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
//#endregion
|
|
758
|
+
//#region ../../plugins/eslint/src/utils/implicit-roles.ts
|
|
759
|
+
const IMPLICIT_ROLES = {
|
|
760
|
+
article: "article",
|
|
761
|
+
aside: "complementary",
|
|
762
|
+
button: "button",
|
|
763
|
+
datalist: "listbox",
|
|
764
|
+
details: "group",
|
|
765
|
+
dialog: "dialog",
|
|
766
|
+
figure: "figure",
|
|
767
|
+
form: "form",
|
|
768
|
+
h1: "heading",
|
|
769
|
+
h2: "heading",
|
|
770
|
+
h3: "heading",
|
|
771
|
+
h4: "heading",
|
|
772
|
+
h5: "heading",
|
|
773
|
+
h6: "heading",
|
|
774
|
+
hr: "separator",
|
|
775
|
+
img: "img",
|
|
776
|
+
li: "listitem",
|
|
777
|
+
main: "main",
|
|
778
|
+
math: "math",
|
|
779
|
+
menu: "list",
|
|
780
|
+
meter: "meter",
|
|
781
|
+
nav: "navigation",
|
|
782
|
+
ol: "list",
|
|
783
|
+
option: "option",
|
|
784
|
+
output: "status",
|
|
785
|
+
progress: "progressbar",
|
|
786
|
+
search: "search",
|
|
787
|
+
section: "region",
|
|
788
|
+
select: "listbox",
|
|
789
|
+
summary: "button",
|
|
790
|
+
table: "table",
|
|
791
|
+
tbody: "rowgroup",
|
|
792
|
+
td: "cell",
|
|
793
|
+
textarea: "textbox",
|
|
794
|
+
tfoot: "rowgroup",
|
|
795
|
+
th: "columnheader",
|
|
796
|
+
thead: "rowgroup",
|
|
797
|
+
tr: "row",
|
|
798
|
+
ul: "list"
|
|
799
|
+
};
|
|
800
|
+
//#endregion
|
|
801
|
+
//#region ../../plugins/eslint/src/rules/no-redundant-role.ts
|
|
802
|
+
const createRule$2 = RuleCreator((name) => `https://praxis-kit.dev/eslint-rules/${name}`);
|
|
803
|
+
function isRedundantRole(explicit, implicit) {
|
|
804
|
+
return explicit === implicit;
|
|
805
|
+
}
|
|
806
|
+
function getJsxTagName(node) {
|
|
807
|
+
const name = node.name;
|
|
808
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
809
|
+
}
|
|
810
|
+
function getJsxStringAttribute(node, attrName) {
|
|
811
|
+
return iterate.find(node.attributes, (attr) => {
|
|
812
|
+
if (attr.type !== "JSXAttribute") return null;
|
|
813
|
+
const { name: key } = attr;
|
|
814
|
+
if (key.type !== "JSXIdentifier" || key.name !== attrName) return null;
|
|
815
|
+
const { value: val } = attr;
|
|
816
|
+
if (val === null) return null;
|
|
817
|
+
if (val.type === "Literal" && isString(val.value)) return {
|
|
818
|
+
node: attr,
|
|
819
|
+
value: val.value
|
|
820
|
+
};
|
|
821
|
+
if (val.type === "JSXExpressionContainer" && val.expression.type === "Literal" && isString(val.expression.value)) return {
|
|
822
|
+
node: attr,
|
|
823
|
+
value: val.expression.value
|
|
824
|
+
};
|
|
825
|
+
return null;
|
|
826
|
+
}) ?? void 0;
|
|
827
|
+
}
|
|
828
|
+
const noRedundantRole = createRule$2({
|
|
829
|
+
name: "no-redundant-role",
|
|
830
|
+
meta: {
|
|
831
|
+
type: "suggestion",
|
|
832
|
+
docs: { description: "Disallow role attributes that duplicate the implicit ARIA role of the HTML element." },
|
|
833
|
+
fixable: "code",
|
|
834
|
+
messages: { redundantRole: EslintDiagnosticTemplates.redundantRole },
|
|
835
|
+
schema: []
|
|
836
|
+
},
|
|
837
|
+
defaultOptions: [],
|
|
838
|
+
create(context) {
|
|
839
|
+
return { JSXOpeningElement(node) {
|
|
840
|
+
const tag = getJsxTagName(node);
|
|
841
|
+
if (!tag) return;
|
|
842
|
+
const implicitRole = IMPLICIT_ROLES[tag];
|
|
843
|
+
if (!implicitRole) return;
|
|
844
|
+
const roleAttr = getJsxStringAttribute(node, "role");
|
|
845
|
+
if (!roleAttr) return;
|
|
846
|
+
if (isRedundantRole(roleAttr.value, implicitRole)) context.report({
|
|
847
|
+
node: roleAttr.node,
|
|
848
|
+
messageId: "redundantRole",
|
|
849
|
+
data: {
|
|
850
|
+
tag,
|
|
851
|
+
role: roleAttr.value
|
|
852
|
+
},
|
|
853
|
+
fix(fixer) {
|
|
854
|
+
return fixer.remove(roleAttr.node);
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
} };
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
const validCardinality = RuleCreator((name) => `https://praxis-kit.dev/eslint-rules/${name}`)({
|
|
861
|
+
name: "valid-cardinality",
|
|
862
|
+
meta: {
|
|
863
|
+
type: "problem",
|
|
864
|
+
docs: { description: "Enforce valid min/max values in enforcement.children cardinality rules." },
|
|
865
|
+
messages: {
|
|
866
|
+
negativeMin: EslintDiagnosticTemplates.negativeMin,
|
|
867
|
+
negativeMax: EslintDiagnosticTemplates.negativeMax,
|
|
868
|
+
maxLessThanMin: EslintDiagnosticTemplates.maxLessThanMin
|
|
869
|
+
},
|
|
870
|
+
schema: [{
|
|
871
|
+
type: "object",
|
|
872
|
+
properties: { calleeNames: {
|
|
873
|
+
type: "array",
|
|
874
|
+
items: { type: "string" }
|
|
875
|
+
} },
|
|
876
|
+
additionalProperties: false
|
|
877
|
+
}]
|
|
878
|
+
},
|
|
879
|
+
defaultOptions: [{}],
|
|
880
|
+
create(context) {
|
|
881
|
+
const calleeNames = new Set(context.options[0]?.calleeNames ?? ["createContractComponent"]);
|
|
882
|
+
function validateCardinality(cardProp) {
|
|
883
|
+
const card = asObjectExpression(cardProp.value);
|
|
884
|
+
if (!card) return;
|
|
885
|
+
const minProp = getObjectProperty(card, "min");
|
|
886
|
+
const maxProp = getObjectProperty(card, "max");
|
|
887
|
+
const min = minProp ? asNumericLiteral(minProp.value) : void 0;
|
|
888
|
+
const max = maxProp ? asNumericLiteral(maxProp.value) : void 0;
|
|
889
|
+
if (minProp && min !== void 0 && min < 0) context.report({
|
|
890
|
+
node: minProp,
|
|
891
|
+
messageId: "negativeMin",
|
|
892
|
+
data: { value: String(min) }
|
|
893
|
+
});
|
|
894
|
+
if (maxProp && max !== void 0 && max < 0) context.report({
|
|
895
|
+
node: maxProp,
|
|
896
|
+
messageId: "negativeMax",
|
|
897
|
+
data: { value: String(max) }
|
|
898
|
+
});
|
|
899
|
+
if (min !== void 0 && max !== void 0 && min >= 0 && max > 0 && max < min) context.report({
|
|
900
|
+
node: cardProp,
|
|
901
|
+
messageId: "maxLessThanMin",
|
|
902
|
+
data: {
|
|
903
|
+
min: String(min),
|
|
904
|
+
max: String(max)
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
return { CallExpression(node) {
|
|
909
|
+
if (!isFactoryCall(node, calleeNames)) return;
|
|
910
|
+
const arg = getFirstObjectArg(node);
|
|
911
|
+
if (!arg) return;
|
|
912
|
+
const enfProp = getObjectProperty(arg, "enforcement");
|
|
913
|
+
if (!enfProp) return;
|
|
914
|
+
const enf = asObjectExpression(enfProp.value);
|
|
915
|
+
if (!enf) return;
|
|
916
|
+
const childrenProp = getObjectProperty(enf, "children");
|
|
917
|
+
if (!childrenProp) return;
|
|
918
|
+
const arr = asArrayExpression(childrenProp.value);
|
|
919
|
+
if (!arr) return;
|
|
920
|
+
iterate.forEach(arr.elements, (element) => {
|
|
921
|
+
if (!element || element.type !== "ObjectExpression") return;
|
|
922
|
+
const cardProp = getObjectProperty(element, "cardinality");
|
|
923
|
+
if (!cardProp) return;
|
|
924
|
+
validateCardinality(cardProp);
|
|
925
|
+
});
|
|
926
|
+
} };
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
//#endregion
|
|
930
|
+
//#region ../../plugins/eslint/src/rules/valid-children-config.ts
|
|
931
|
+
const createRule = RuleCreator((name) => `https://praxis-kit.dev/eslint-rules/${name}`);
|
|
932
|
+
function analyzeChildrenRules(elements) {
|
|
933
|
+
const firstPositionProps = [];
|
|
934
|
+
const lastPositionProps = [];
|
|
935
|
+
let onlyWithMinProp = null;
|
|
936
|
+
let requiredRuleCount = 0;
|
|
937
|
+
iterate.forEach(elements, (element) => {
|
|
938
|
+
if (!element || element.type !== "ObjectExpression") return;
|
|
939
|
+
const positionProp = getObjectProperty(element, "position");
|
|
940
|
+
const position = positionProp ? asStringLiteral(positionProp.value) : void 0;
|
|
941
|
+
const cardProp = getObjectProperty(element, "cardinality");
|
|
942
|
+
const card = cardProp ? asObjectExpression(cardProp.value) : void 0;
|
|
943
|
+
const minProp = card ? getObjectProperty(card, "min") : void 0;
|
|
944
|
+
const min = minProp ? asNumericLiteral(minProp.value) ?? 0 : 0;
|
|
945
|
+
if (position === "first" && positionProp) firstPositionProps.push(positionProp);
|
|
946
|
+
if (position === "last" && positionProp) lastPositionProps.push(positionProp);
|
|
947
|
+
if (min >= 1) {
|
|
948
|
+
requiredRuleCount++;
|
|
949
|
+
if (position === "only" && positionProp && !onlyWithMinProp) onlyWithMinProp = positionProp;
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
return {
|
|
953
|
+
firstPositionProps,
|
|
954
|
+
lastPositionProps,
|
|
955
|
+
onlyWithMinProp,
|
|
956
|
+
requiredRuleCount
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
const validChildrenConfig = createRule({
|
|
960
|
+
name: "valid-children-config",
|
|
961
|
+
meta: {
|
|
962
|
+
type: "problem",
|
|
963
|
+
docs: { description: "Enforce cross-rule consistency of enforcement.children — detect positional conflicts and cardinality impossibilities." },
|
|
964
|
+
messages: {
|
|
965
|
+
multipleFirst: EslintDiagnosticTemplates.multipleFirst,
|
|
966
|
+
multipleLast: EslintDiagnosticTemplates.multipleLast,
|
|
967
|
+
minSumExceedsCapacity: EslintDiagnosticTemplates.minSumExceedsCapacity
|
|
968
|
+
},
|
|
969
|
+
schema: [{
|
|
970
|
+
type: "object",
|
|
971
|
+
properties: { calleeNames: {
|
|
972
|
+
type: "array",
|
|
973
|
+
items: { type: "string" }
|
|
974
|
+
} },
|
|
975
|
+
additionalProperties: false
|
|
976
|
+
}]
|
|
977
|
+
},
|
|
978
|
+
defaultOptions: [{}],
|
|
979
|
+
create(context) {
|
|
980
|
+
const calleeNames = new Set(context.options[0]?.calleeNames ?? ["createContractComponent"]);
|
|
981
|
+
return { CallExpression(node) {
|
|
982
|
+
if (!isFactoryCall(node, calleeNames)) return;
|
|
983
|
+
const arg = getFirstObjectArg(node);
|
|
984
|
+
if (!arg) return;
|
|
985
|
+
const enfProp = getObjectProperty(arg, "enforcement");
|
|
986
|
+
if (!enfProp) return;
|
|
987
|
+
const enf = asObjectExpression(enfProp.value);
|
|
988
|
+
if (!enf) return;
|
|
989
|
+
const childrenProp = getObjectProperty(enf, "children");
|
|
990
|
+
if (!childrenProp) return;
|
|
991
|
+
const arr = asArrayExpression(childrenProp.value);
|
|
992
|
+
if (!arr) return;
|
|
993
|
+
const { firstPositionProps, lastPositionProps, onlyWithMinProp, requiredRuleCount } = analyzeChildrenRules(arr.elements);
|
|
994
|
+
iterate.forEach(firstPositionProps.slice(1), (prop) => {
|
|
995
|
+
context.report({
|
|
996
|
+
node: prop,
|
|
997
|
+
messageId: "multipleFirst"
|
|
998
|
+
});
|
|
999
|
+
});
|
|
1000
|
+
iterate.forEach(lastPositionProps.slice(1), (prop) => {
|
|
1001
|
+
context.report({
|
|
1002
|
+
node: prop,
|
|
1003
|
+
messageId: "multipleLast"
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
if (onlyWithMinProp && requiredRuleCount > 1) context.report({
|
|
1007
|
+
node: onlyWithMinProp,
|
|
1008
|
+
messageId: "minSumExceedsCapacity",
|
|
1009
|
+
data: { count: String(requiredRuleCount - 1) }
|
|
1010
|
+
});
|
|
1011
|
+
} };
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
//#endregion
|
|
1015
|
+
//#region ../../plugins/eslint/src/index.ts
|
|
1016
|
+
const plugin = {
|
|
1017
|
+
meta: {
|
|
1018
|
+
name: "@praxis-kit/eslint-plugin",
|
|
1019
|
+
version: "1.0.0"
|
|
1020
|
+
},
|
|
1021
|
+
rules: {
|
|
1022
|
+
"no-dead-compound": noDeadCompound,
|
|
1023
|
+
"no-enforcement-without-strict": noEnforcementWithoutStrict,
|
|
1024
|
+
"no-invalid-default": noInvalidDefault,
|
|
1025
|
+
"no-invalid-html-nesting": noInvalidHtmlNesting,
|
|
1026
|
+
"no-redundant-role": noRedundantRole,
|
|
1027
|
+
"valid-cardinality": validCardinality,
|
|
1028
|
+
"valid-children-config": validChildrenConfig
|
|
1029
|
+
},
|
|
1030
|
+
configs: {}
|
|
1031
|
+
};
|
|
1032
|
+
const recommended = {
|
|
1033
|
+
name: "@praxis-kit/recommended",
|
|
1034
|
+
plugins: { "@praxis-kit": plugin },
|
|
1035
|
+
rules: {
|
|
1036
|
+
"@praxis-kit/no-dead-compound": "error",
|
|
1037
|
+
"@praxis-kit/no-enforcement-without-strict": "error",
|
|
1038
|
+
"@praxis-kit/no-invalid-default": "error",
|
|
1039
|
+
"@praxis-kit/no-invalid-html-nesting": "error",
|
|
1040
|
+
"@praxis-kit/no-redundant-role": "warn",
|
|
1041
|
+
"@praxis-kit/valid-cardinality": "error",
|
|
1042
|
+
"@praxis-kit/valid-children-config": "error"
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
plugin.configs["recommended"] = recommended;
|
|
1046
|
+
//#endregion
|
|
1047
|
+
export { plugin as default, plugin, noDeadCompound, noEnforcementWithoutStrict, noInvalidDefault, noInvalidHtmlNesting, noRedundantRole, recommended, validCardinality, validChildrenConfig };
|