@estiva-app/ui 0.30.0 → 0.31.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 +5 -1
- package/dist/CommandPalette.d.ts.map +1 -1
- package/dist/DialogShell.d.ts.map +1 -1
- package/dist/eslint/index.d.ts +1 -0
- package/dist/eslint/index.d.ts.map +1 -1
- package/dist/eslint/index.js +119 -22
- package/dist/eslint/index.js.map +4 -4
- package/dist/eslint/no-handmade-header.d.ts +17 -0
- package/dist/eslint/no-handmade-header.d.ts.map +1 -0
- package/dist/gates/app-checks.d.ts +1 -1
- package/dist/gates/app-checks.d.ts.map +1 -1
- package/dist/gates/index.js +4 -2
- package/dist/gates/index.js.map +2 -2
- package/dist/index.js +142 -123
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/registry.json +1 -1
- package/src/CommandPalette.tsx +1 -0
- package/src/ContainerHeader.mdx +3 -2
- package/src/DialogShell.tsx +22 -17
- package/src/eslint/index.test.ts +13 -9
- package/src/eslint/index.ts +4 -0
- package/src/eslint/no-handmade-header.test.ts +89 -0
- package/src/eslint/no-handmade-header.ts +153 -0
- package/src/gates/app-checks.ts +5 -3
- package/src/gates/create-app.test.ts +1 -1
- package/src/gates/gates.test.ts +1 -1
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ export default {
|
|
|
64
64
|
|
|
65
65
|
The UI Guardrails' rules ship with the package, as an ESLint plugin, so an app
|
|
66
66
|
gets a new rule with a version bump. Each rule names the component to use
|
|
67
|
-
instead. There are
|
|
67
|
+
instead. There are four:
|
|
68
68
|
|
|
69
69
|
- `estiva/no-raw-element` refuses a raw interactive element (`<a>`, `<input>`,
|
|
70
70
|
`<form>`, `<dialog>`…) and names the part to use, or says the package has none yet.
|
|
@@ -79,6 +79,10 @@ instead. There are three:
|
|
|
79
79
|
`className` or an inner box's class prop — a colour, a text size, a border, a
|
|
80
80
|
corner, a shadow — and names the part's look props (`PART_LOOK_PROPS`). Only
|
|
81
81
|
placement goes through (`PLACEMENT`), and `EmptyState` takes no padding.
|
|
82
|
+
- `estiva/no-handmade-header` refuses a column's header bar drawn by hand: a
|
|
83
|
+
row first in a column, with its own side padding and a title, where the column
|
|
84
|
+
fills its height or the row has a bar's height or hairline. It reads the
|
|
85
|
+
shape, not the class names, and names `ContainerHeader`.
|
|
82
86
|
|
|
83
87
|
```js
|
|
84
88
|
// eslint.config.js — alongside your own rules
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommandPalette.d.ts","sourceRoot":"","sources":["../src/CommandPalette.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"CommandPalette.d.ts","sourceRoot":"","sources":["../src/CommandPalette.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DialogShell.d.ts","sourceRoot":"","sources":["../src/DialogShell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"DialogShell.d.ts","sourceRoot":"","sources":["../src/DialogShell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAS9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;8CAC0C;IAC1C,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB;yDACqD;IACrD,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB;uCACmC;IACnC,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,QAAQ,EAAE,SAAS,CAAA;IACnB,0GAA0G;IAC1G,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,KAAW,EAAE,KAAa,EAAE,EAAE,gBAAgB,+BAsG1J"}
|
package/dist/eslint/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ declare const plugin: {
|
|
|
15
15
|
'component-has-a-page': import("eslint").Rule.RuleModule;
|
|
16
16
|
'component-has-a-story': import("eslint").Rule.RuleModule;
|
|
17
17
|
'no-restyled-part': import("eslint").Rule.RuleModule;
|
|
18
|
+
'no-handmade-header': import("eslint").Rule.RuleModule;
|
|
18
19
|
'no-raw-element': import("eslint").Rule.RuleModule;
|
|
19
20
|
'no-rebuilt-behaviour': import("eslint").Rule.RuleModule;
|
|
20
21
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/eslint/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/eslint/index.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAS5C,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAA;AAClG,OAAO,EAAE,gBAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAC9E,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAI/D,2FAA2F;AAC3F,eAAO,MAAM,UAAU,WAAW,CAAA;AAyClC,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;aAGK;QAAE,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAA;KAAE;CACrE,CAAA;AAEzB,qFAAqF;AACrF,eAAO,MAAM,YAAY,UAA+D,CAAA;AACxF,wFAAwF;AACxF,eAAO,MAAM,gBAAgB,UAAmE,CAAA;AA+BhG,eAAe,MAAM,CAAA;AAErB,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IACpC;;;OAGG;IACH,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,IAAI,GAAE,SAAS,MAAM,EAAiB,GAAG,SAAS,CAmB1G"}
|
package/dist/eslint/index.js
CHANGED
|
@@ -158,6 +158,100 @@ var noHandRolledBehaviour = {
|
|
|
158
158
|
}
|
|
159
159
|
};
|
|
160
160
|
|
|
161
|
+
// src/eslint/no-handmade-header.ts
|
|
162
|
+
var isElement = (n) => typeof n === "object" && n !== null && n.type === "JSXElement";
|
|
163
|
+
function tagOf(el) {
|
|
164
|
+
const n = el.openingElement.name;
|
|
165
|
+
if (n.type === "JSXIdentifier") return n.name ?? "";
|
|
166
|
+
if (n.type === "JSXMemberExpression") return `${n.object?.name ?? ""}.${n.property?.name ?? ""}`;
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
function attribute(el, name) {
|
|
170
|
+
return el.openingElement.attributes.find((a) => a.type === "JSXAttribute" && a.name?.name === name);
|
|
171
|
+
}
|
|
172
|
+
function strings(node, out = []) {
|
|
173
|
+
if (!node || typeof node !== "object") return out;
|
|
174
|
+
if (node.type === "Literal" && typeof node.value === "string") out.push(node.value);
|
|
175
|
+
else if (node.type === "TemplateLiteral") for (const q of node.quasis) out.push(q.value.cooked ?? "");
|
|
176
|
+
for (const [key, value] of Object.entries(node)) {
|
|
177
|
+
if (key === "parent" || key === "loc" || key === "range") continue;
|
|
178
|
+
if (Array.isArray(value)) for (const v of value) strings(v, out);
|
|
179
|
+
else if (value && typeof value === "object" && value.type) strings(value, out);
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
function classesOf(el) {
|
|
184
|
+
const value = attribute(el, "className")?.value;
|
|
185
|
+
if (!value) return [];
|
|
186
|
+
const text = value.type === "Literal" ? String(value.value) : strings(value.expression).join(" ");
|
|
187
|
+
return text.split(/\s+/).filter(Boolean).map((c) => c.replace(/^(?:[^:\s[\]]+:)+/, ""));
|
|
188
|
+
}
|
|
189
|
+
function meaningful(el) {
|
|
190
|
+
const out = [];
|
|
191
|
+
for (const child2 of el.children) {
|
|
192
|
+
if (child2.type === "JSXText") {
|
|
193
|
+
if (String(child2.value).trim()) out.push(child2);
|
|
194
|
+
} else if (child2.type === "JSXExpressionContainer") {
|
|
195
|
+
const e = child2.expression;
|
|
196
|
+
if (e.type === "JSXEmptyExpression") continue;
|
|
197
|
+
if (e.type === "LogicalExpression" && isElement(e.right)) out.push(e.right);
|
|
198
|
+
else if (isElement(e)) out.push(e);
|
|
199
|
+
else out.push(child2);
|
|
200
|
+
} else if (child2.type === "JSXElement" || child2.type === "JSXFragment") out.push(child2);
|
|
201
|
+
}
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
var TEXT_PARTS = /* @__PURE__ */ new Set(["EditableText", "Person", "Breadcrumb", "Link", "SectionLabel", "InlineChip"]);
|
|
205
|
+
var VALUE_EXPRESSIONS = /* @__PURE__ */ new Set(["Identifier", "MemberExpression", "TemplateLiteral", "Literal", "LogicalExpression", "ConditionalExpression", "ChainExpression", "CallExpression"]);
|
|
206
|
+
function carriesTitle(el, depth = 0) {
|
|
207
|
+
if (depth > 3) return false;
|
|
208
|
+
for (const child2 of meaningful(el)) {
|
|
209
|
+
if (child2.type === "JSXText") return true;
|
|
210
|
+
if (child2.type === "JSXExpressionContainer") {
|
|
211
|
+
if (VALUE_EXPRESSIONS.has(child2.expression.type)) return true;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (!isElement(child2)) continue;
|
|
215
|
+
const tag = tagOf(child2);
|
|
216
|
+
if (/^h[1-6]$/.test(tag) || TEXT_PARTS.has(tag) || /\.(?:Title|Heading)$/.test(tag)) return true;
|
|
217
|
+
if (/^[a-z]/.test(tag) && carriesTitle(child2, depth + 1)) return true;
|
|
218
|
+
}
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
var ROW = (c) => c.includes("flex") && !c.includes("flex-col");
|
|
222
|
+
var OWN_SIDE_PADDING = (c) => c.some((x) => /^-?(?:p|px|pl|pr|ps|pe)-/.test(x));
|
|
223
|
+
var FILLS = (c) => c.includes("flex-col") && c.some((x) => /^(?:h-full|h-screen|flex-1|grow|min-h-0|h-dvh)$/.test(x));
|
|
224
|
+
var BAR = (c) => c.some((x) => /^(?:h|min-h)-(?:10|11|12|13|14|\[(?:4[0-9]|5[0-6])px\])$/.test(x)) || c.some((x) => /^border-b(?:-|$)/.test(x));
|
|
225
|
+
var INTERACTIVE = ["onClick", "href", "role", "onKeyDown", "onPointerDown"];
|
|
226
|
+
var noHandmadeHeader = {
|
|
227
|
+
meta: {
|
|
228
|
+
type: "problem",
|
|
229
|
+
docs: { description: "A column's header bar drawn by hand where ContainerHeader is the part" },
|
|
230
|
+
schema: [],
|
|
231
|
+
messages: {
|
|
232
|
+
handmade: "A column's header bar drawn by hand. Use `ContainerHeader` from @estiva-app/ui: 48px, the title, the column's buttons at the right edge (`actions`), a hairline under it.",
|
|
233
|
+
...ESCAPE_MESSAGES
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
create(context) {
|
|
237
|
+
return {
|
|
238
|
+
JSXElement(node) {
|
|
239
|
+
const column = node;
|
|
240
|
+
const kids = meaningful(column);
|
|
241
|
+
const first = kids[0];
|
|
242
|
+
if (kids.length < 2 || !isElement(first) || !/^[a-z]/.test(tagOf(first))) return;
|
|
243
|
+
const own = classesOf(first);
|
|
244
|
+
if (!ROW(own) || !OWN_SIDE_PADDING(own)) return;
|
|
245
|
+
if (INTERACTIVE.some((name) => attribute(first, name))) return;
|
|
246
|
+
if (!FILLS(classesOf(column)) && !BAR(own)) return;
|
|
247
|
+
if (!carriesTitle(first)) return;
|
|
248
|
+
if (isEscaped(context, first.openingElement)) return;
|
|
249
|
+
context.report({ loc: first.openingElement.loc, messageId: "handmade" });
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
161
255
|
// src/eslint/no-raw-element.ts
|
|
162
256
|
var RAW_ELEMENT_PARTS = {
|
|
163
257
|
a: { use: "Link", more: " For a chip, `InlineChip`; for a whole card, `Card` with `href`." },
|
|
@@ -199,7 +293,7 @@ var ANY_INPUT = {
|
|
|
199
293
|
use: "TextInput",
|
|
200
294
|
more: " For a search, `SearchInput`; for a tick box, `Checkbox`; to pick files, `FilePicker`."
|
|
201
295
|
};
|
|
202
|
-
function
|
|
296
|
+
function attribute2(element, name) {
|
|
203
297
|
return element.attributes.find((a) => a.type === "JSXAttribute" && a.name.name === name);
|
|
204
298
|
}
|
|
205
299
|
function literal(attr) {
|
|
@@ -213,14 +307,14 @@ function literal(attr) {
|
|
|
213
307
|
return void 0;
|
|
214
308
|
}
|
|
215
309
|
function isOn(element, name) {
|
|
216
|
-
const attr =
|
|
310
|
+
const attr = attribute2(element, name);
|
|
217
311
|
return attr !== void 0 && literal(attr) !== false;
|
|
218
312
|
}
|
|
219
313
|
function classify(element) {
|
|
220
314
|
if (element.name.type !== "JSXIdentifier" || !element.name.name) return null;
|
|
221
315
|
const name = element.name.name;
|
|
222
316
|
if (name === "input") {
|
|
223
|
-
const attr =
|
|
317
|
+
const attr = attribute2(element, "type");
|
|
224
318
|
const type = attr ? literal(attr) : "text";
|
|
225
319
|
if (typeof type !== "string") return { report: "raw", element: "<input>", part: ANY_INPUT };
|
|
226
320
|
const key = type.toLowerCase();
|
|
@@ -232,7 +326,7 @@ function classify(element) {
|
|
|
232
326
|
}
|
|
233
327
|
if (!Object.hasOwn(RAW_ELEMENT_PARTS, name)) return null;
|
|
234
328
|
if ((name === "audio" || name === "video") && !isOn(element, "controls")) return null;
|
|
235
|
-
if (name === "img" && !
|
|
329
|
+
if (name === "img" && !attribute2(element, "usemap") && !attribute2(element, "useMap")) return null;
|
|
236
330
|
const part = RAW_ELEMENT_PARTS[name];
|
|
237
331
|
return part ? { report: "raw", element: `<${name}>`, part } : { report: "noPart", element: `<${name}>` };
|
|
238
332
|
}
|
|
@@ -1050,18 +1144,18 @@ function handsOnTo(fn, bindingOf) {
|
|
|
1050
1144
|
if (wrapper || node.type !== "JSXOpeningElement") return;
|
|
1051
1145
|
const binding = bindingOf(child(node, "name"));
|
|
1052
1146
|
if (!binding) return;
|
|
1053
|
-
for (const
|
|
1054
|
-
if (!
|
|
1055
|
-
if (
|
|
1056
|
-
const argument = nameOf(child(
|
|
1147
|
+
for (const attribute3 of children(node, "attributes")) {
|
|
1148
|
+
if (!attribute3) continue;
|
|
1149
|
+
if (attribute3.type === "JSXSpreadAttribute") {
|
|
1150
|
+
const argument = nameOf(child(attribute3, "argument"));
|
|
1057
1151
|
if (argument && (argument === restName || argument === propsName)) wrapper = { part: binding.part, props: binding.props };
|
|
1058
1152
|
continue;
|
|
1059
1153
|
}
|
|
1060
|
-
const prop = nameOf(child(
|
|
1154
|
+
const prop = nameOf(child(attribute3, "name"));
|
|
1061
1155
|
if (!prop || !CLASS_PROP.test(prop)) continue;
|
|
1062
1156
|
const partProp = binding.props === "all" ? prop : binding.props[prop];
|
|
1063
1157
|
if (!partProp) continue;
|
|
1064
|
-
walk(
|
|
1158
|
+
walk(attribute3, (inner) => {
|
|
1065
1159
|
let callerProp;
|
|
1066
1160
|
if (inner.type === "Identifier") callerProp = classParams.get(inner.name);
|
|
1067
1161
|
if (inner.type === "MemberExpression" && nameOf(child(inner, "object")) === propsName) {
|
|
@@ -1093,10 +1187,10 @@ function constantOf(identifier, scope) {
|
|
|
1093
1187
|
}
|
|
1094
1188
|
return void 0;
|
|
1095
1189
|
}
|
|
1096
|
-
function
|
|
1190
|
+
function classesOf2(value, scopeOf, asMap = false, seen = /* @__PURE__ */ new Set()) {
|
|
1097
1191
|
if (!value || seen.has(value) || seen.size > 200) return [];
|
|
1098
1192
|
seen.add(value);
|
|
1099
|
-
const read = (node, map = false) =>
|
|
1193
|
+
const read = (node, map = false) => classesOf2(node, scopeOf, map, seen);
|
|
1100
1194
|
const words = (text) => typeof text === "string" ? text.split(/\s+/).filter(Boolean) : [];
|
|
1101
1195
|
switch (value.type) {
|
|
1102
1196
|
case "Literal":
|
|
@@ -1174,7 +1268,7 @@ var noRestyledPart = {
|
|
|
1174
1268
|
const check = (element, binding, prop, value, at) => {
|
|
1175
1269
|
const partProp = binding.props === "all" ? prop : binding.props[prop];
|
|
1176
1270
|
if (!partProp || !value) return;
|
|
1177
|
-
const classes =
|
|
1271
|
+
const classes = classesOf2(value, scopeOf);
|
|
1178
1272
|
const handedOn = binding.props === "all" ? "its props" : `its \`${prop}\``;
|
|
1179
1273
|
const via = binding.via && binding.via !== binding.part ? `\`${binding.via}\` (it hands ${handedOn} to \`${binding.part}\`)` : `\`${binding.part}\``;
|
|
1180
1274
|
const where = `${via}${partProp === "className" ? "" : `'s \`${partProp}\``}`;
|
|
@@ -1197,14 +1291,14 @@ var noRestyledPart = {
|
|
|
1197
1291
|
const element = ruleNode;
|
|
1198
1292
|
const binding = locals && bindingOfTag(child(element, "name"), locals);
|
|
1199
1293
|
if (!binding) return;
|
|
1200
|
-
for (const
|
|
1201
|
-
if (!
|
|
1202
|
-
if (
|
|
1203
|
-
const prop = nameOf(child(
|
|
1204
|
-
if (prop && CLASS_PROP.test(prop)) check(element, binding, prop, child(
|
|
1294
|
+
for (const attribute3 of children(element, "attributes")) {
|
|
1295
|
+
if (!attribute3) continue;
|
|
1296
|
+
if (attribute3.type === "JSXAttribute") {
|
|
1297
|
+
const prop = nameOf(child(attribute3, "name"));
|
|
1298
|
+
if (prop && CLASS_PROP.test(prop)) check(element, binding, prop, child(attribute3, "value"), attribute3);
|
|
1205
1299
|
continue;
|
|
1206
1300
|
}
|
|
1207
|
-
let spread = child(
|
|
1301
|
+
let spread = child(attribute3, "argument");
|
|
1208
1302
|
if (spread?.type === "Identifier") spread = constantOf(spread, scopeOf(spread));
|
|
1209
1303
|
while (spread?.type === "TSAsExpression" || spread?.type === "TSSatisfiesExpression") spread = child(spread, "expression");
|
|
1210
1304
|
if (spread?.type !== "ObjectExpression") continue;
|
|
@@ -1260,14 +1354,16 @@ var PLUGIN_KEY = "estiva";
|
|
|
1260
1354
|
var appRules = {
|
|
1261
1355
|
"no-raw-element": noRawElement,
|
|
1262
1356
|
"no-rebuilt-behaviour": noRebuiltBehaviour,
|
|
1263
|
-
"no-restyled-part": noRestyledPart
|
|
1357
|
+
"no-restyled-part": noRestyledPart,
|
|
1358
|
+
"no-handmade-header": noHandmadeHeader
|
|
1264
1359
|
};
|
|
1265
1360
|
var packageRules = {
|
|
1266
1361
|
"raw-element-outside-a-wrapper": rawElementOutsideAWrapper,
|
|
1267
1362
|
"no-hand-rolled-behaviour": noHandRolledBehaviour,
|
|
1268
1363
|
"component-has-a-page": componentHasAPage,
|
|
1269
1364
|
"component-has-a-story": componentHasAStory,
|
|
1270
|
-
"no-restyled-part": noRestyledPart
|
|
1365
|
+
"no-restyled-part": noRestyledPart,
|
|
1366
|
+
"no-handmade-header": noHandmadeHeader
|
|
1271
1367
|
};
|
|
1272
1368
|
var rules = { ...appRules, ...packageRules };
|
|
1273
1369
|
var plugin = {
|
|
@@ -1283,7 +1379,8 @@ plugin.configs.recommended = {
|
|
|
1283
1379
|
rules: {
|
|
1284
1380
|
[`${PLUGIN_KEY}/no-raw-element`]: "error",
|
|
1285
1381
|
[`${PLUGIN_KEY}/no-rebuilt-behaviour`]: "error",
|
|
1286
|
-
[`${PLUGIN_KEY}/no-restyled-part`]: "error"
|
|
1382
|
+
[`${PLUGIN_KEY}/no-restyled-part`]: "error",
|
|
1383
|
+
[`${PLUGIN_KEY}/no-handmade-header`]: "error"
|
|
1287
1384
|
}
|
|
1288
1385
|
};
|
|
1289
1386
|
plugin.configs.strict = {
|