@estiva-app/ui 0.33.0 → 0.35.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 +8 -1
- package/dist/AttachmentCard.d.ts.map +1 -1
- package/dist/Button.d.ts.map +1 -1
- package/dist/Card.d.ts.map +1 -1
- package/dist/Chip.d.ts.map +1 -1
- package/dist/ChipInput.d.ts.map +1 -1
- package/dist/CommandPalette.d.ts.map +1 -1
- package/dist/DialogShell.d.ts.map +1 -1
- package/dist/Lightbox.d.ts.map +1 -1
- package/dist/Menu.d.ts.map +1 -1
- package/dist/NavItem.d.ts.map +1 -1
- package/dist/PersonTrigger.d.ts.map +1 -1
- package/dist/Reaction.d.ts.map +1 -1
- package/dist/SearchInput.d.ts.map +1 -1
- package/dist/SectionLabel.d.ts +9 -2
- package/dist/SectionLabel.d.ts.map +1 -1
- package/dist/Select.d.ts.map +1 -1
- package/dist/TextInput.d.ts.map +1 -1
- package/dist/Textarea.d.ts.map +1 -1
- package/dist/Tooltip.d.ts.map +1 -1
- package/dist/eslint/index.d.ts +2 -0
- package/dist/eslint/index.d.ts.map +1 -1
- package/dist/eslint/index.js +392 -14
- package/dist/eslint/index.js.map +4 -4
- package/dist/eslint/looks-of.d.ts +54 -0
- package/dist/eslint/looks-of.d.ts.map +1 -0
- package/dist/eslint/no-copied-look.d.ts +3 -0
- package/dist/eslint/no-copied-look.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/{chunk-JZE7TIQY.js → chunk-D466XXVL.js} +12 -6
- package/dist/gates/chunk-D466XXVL.js.map +7 -0
- package/dist/gates/{chunk-XAPFC37F.js → chunk-JWOU4UQZ.js} +43 -8
- package/dist/gates/chunk-JWOU4UQZ.js.map +7 -0
- package/dist/gates/cli.js +12 -4
- package/dist/gates/cli.js.map +2 -2
- package/dist/gates/create-app.d.ts.map +1 -1
- package/dist/gates/create-app.js +1 -1
- package/dist/gates/gate-config.d.ts +1 -1
- package/dist/gates/hook.d.ts +1 -1
- package/dist/gates/hook.d.ts.map +1 -1
- package/dist/gates/index.js +3 -3
- package/dist/gates/index.js.map +2 -2
- package/dist/gates/status.d.ts +9 -3
- package/dist/gates/status.d.ts.map +1 -1
- package/dist/index.js +171 -133
- package/dist/index.js.map +3 -3
- package/dist/looks.d.ts +34 -0
- package/dist/looks.d.ts.map +1 -0
- package/dist/registry/app-3ALZE6XO.js +10 -0
- package/dist/registry/app.d.ts.map +1 -1
- package/dist/registry/{build-LGFCCOLR.js → build-NDXMVFQ5.js} +3 -3
- package/dist/registry/build.d.ts.map +1 -1
- package/dist/registry/{chunk-XCBN2EKW.js → chunk-3WJ7ZZ7Z.js} +8 -4
- package/dist/registry/chunk-3WJ7ZZ7Z.js.map +7 -0
- package/dist/registry/{chunk-E4JNV7PC.js → chunk-PZO4BVV3.js} +2 -1
- package/dist/registry/chunk-PZO4BVV3.js.map +7 -0
- package/dist/registry/chunk-XVII7P6G.js +595 -0
- package/dist/registry/chunk-XVII7P6G.js.map +7 -0
- package/dist/registry/cli.js +106 -18
- package/dist/registry/cli.js.map +4 -4
- package/dist/registry/index.js +3 -3
- package/dist/registry/schema.d.ts +8 -0
- package/dist/registry/schema.d.ts.map +1 -1
- package/dist/registry/storymap.d.ts +12 -0
- package/dist/registry/storymap.d.ts.map +1 -0
- package/package.json +1 -1
- package/registry.json +347 -90
- package/src/AttachmentCard.tsx +2 -0
- package/src/Button.tsx +1 -0
- package/src/Card.stories.tsx +2 -2
- package/src/Card.tsx +1 -0
- package/src/Chip.tsx +2 -1
- package/src/ChipInput.tsx +4 -2
- package/src/CommandPalette.tsx +2 -0
- package/src/ContainerHeader.stories.tsx +2 -1
- package/src/DialogShell.tsx +1 -0
- package/src/Divider.stories.tsx +12 -10
- package/src/Lightbox.tsx +1 -0
- package/src/Link.stories.tsx +5 -4
- package/src/Menu.tsx +3 -1
- package/src/NavItem.tsx +1 -0
- package/src/PersonTrigger.tsx +1 -0
- package/src/Reaction.tsx +3 -1
- package/src/ReactionPicker.stories.tsx +3 -2
- package/src/ScrollArea.stories.tsx +13 -12
- package/src/SearchInput.tsx +5 -4
- package/src/SectionLabel.mdx +4 -2
- package/src/SectionLabel.stories.tsx +13 -0
- package/src/SectionLabel.tsx +19 -2
- package/src/Select.tsx +6 -4
- package/src/TextInput.tsx +6 -6
- package/src/Textarea.tsx +6 -4
- package/src/Tooltip.stories.tsx +3 -2
- package/src/Tooltip.tsx +2 -1
- package/src/eslint/index.test.ts +12 -8
- package/src/eslint/index.ts +13 -5
- package/src/eslint/looks-of.ts +302 -0
- package/src/eslint/no-copied-look.test.ts +155 -0
- package/src/eslint/no-copied-look.ts +203 -0
- package/src/eslint/no-restyled-part.ts +1 -1
- package/src/gates/app-checks.ts +2 -2
- package/src/gates/cli.ts +15 -4
- package/src/gates/create-app.test.ts +57 -2
- package/src/gates/create-app.ts +11 -5
- package/src/gates/gate-config.ts +1 -1
- package/src/gates/gates.test.ts +36 -3
- package/src/gates/hook.ts +1 -1
- package/src/gates/status.ts +62 -17
- package/src/looks.ts +42 -0
- package/src/registry/app.ts +3 -0
- package/src/registry/build.ts +8 -0
- package/src/registry/cli.ts +21 -3
- package/src/registry/registry.test.ts +1 -1
- package/src/registry/schema.ts +9 -0
- package/src/registry/storymap.test.ts +96 -0
- package/src/registry/storymap.ts +107 -0
- package/dist/gates/chunk-JZE7TIQY.js.map +0 -7
- package/dist/gates/chunk-XAPFC37F.js.map +0 -7
- package/dist/registry/app-FXN4EI34.js +0 -10
- package/dist/registry/chunk-E4JNV7PC.js.map +0 -7
- package/dist/registry/chunk-NJN4MQAM.js +0 -397
- package/dist/registry/chunk-NJN4MQAM.js.map +0 -7
- package/dist/registry/chunk-XCBN2EKW.js.map +0 -7
- /package/dist/registry/{app-FXN4EI34.js.map → app-3ALZE6XO.js.map} +0 -0
- /package/dist/registry/{build-LGFCCOLR.js.map → build-NDXMVFQ5.js.map} +0 -0
package/dist/eslint/index.js
CHANGED
|
@@ -224,7 +224,7 @@ var PART_LOOK_PROPS = {
|
|
|
224
224
|
Reaction: ["pressed"],
|
|
225
225
|
ReactionPicker: ["surface"],
|
|
226
226
|
SectionHeader: ["hover"],
|
|
227
|
-
SectionLabel: ["tone"],
|
|
227
|
+
SectionLabel: ["tone", "truncate"],
|
|
228
228
|
Select: ["size"],
|
|
229
229
|
Tabs: ["size"],
|
|
230
230
|
TextInput: ["size"],
|
|
@@ -547,12 +547,12 @@ function classesOf(value, scopeOf, asMap = false, seen = /* @__PURE__ */ new Set
|
|
|
547
547
|
if (!value || seen.has(value) || seen.size > 200) return [];
|
|
548
548
|
seen.add(value);
|
|
549
549
|
const read = (node, map = false) => classesOf(node, scopeOf, map, seen);
|
|
550
|
-
const
|
|
550
|
+
const words2 = (text) => typeof text === "string" ? text.split(/\s+/).filter(Boolean) : [];
|
|
551
551
|
switch (value.type) {
|
|
552
552
|
case "Literal":
|
|
553
|
-
return
|
|
553
|
+
return words2(value.value);
|
|
554
554
|
case "TemplateLiteral":
|
|
555
|
-
return [...children(value, "quasis").flatMap((quasi) =>
|
|
555
|
+
return [...children(value, "quasis").flatMap((quasi) => words2((quasi?.value).cooked)), ...children(value, "expressions").flatMap((e) => read(e))];
|
|
556
556
|
case "JSXExpressionContainer":
|
|
557
557
|
case "TSAsExpression":
|
|
558
558
|
case "TSSatisfiesExpression":
|
|
@@ -570,7 +570,7 @@ function classesOf(value, scopeOf, asMap = false, seen = /* @__PURE__ */ new Set
|
|
|
570
570
|
case "ObjectExpression":
|
|
571
571
|
return children(value, "properties").flatMap((property) => {
|
|
572
572
|
if (property?.type !== "Property") return [];
|
|
573
|
-
return asMap ? read(child(property, "value"), true) : property.computed ? [] :
|
|
573
|
+
return asMap ? read(child(property, "value"), true) : property.computed ? [] : words2(nameOf(child(property, "key")));
|
|
574
574
|
});
|
|
575
575
|
case "CallExpression": {
|
|
576
576
|
const callee = child(value, "callee");
|
|
@@ -852,8 +852,8 @@ var noHandmadeEmptyState = {
|
|
|
852
852
|
const opening = child2(element, "openingElement");
|
|
853
853
|
const tag = tagName(element);
|
|
854
854
|
if (tag && /^[a-z]/.test(tag)) {
|
|
855
|
-
const
|
|
856
|
-
if (!
|
|
855
|
+
const words2 = onlyWords(element);
|
|
856
|
+
if (!words2 || words2.length > SHORT) return;
|
|
857
857
|
if (!standsInForAList(element)) return;
|
|
858
858
|
if (isEscaped(context, opening)) return;
|
|
859
859
|
context.report({ loc: opening.loc, messageId: "handmade" });
|
|
@@ -984,10 +984,381 @@ var noHandmadeHeader = {
|
|
|
984
984
|
}
|
|
985
985
|
};
|
|
986
986
|
|
|
987
|
+
// src/eslint/no-copied-look.ts
|
|
988
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync, statSync as statSync3 } from "node:fs";
|
|
989
|
+
import { dirname as dirname3, join as join3, relative, resolve as resolve2 } from "node:path";
|
|
990
|
+
|
|
991
|
+
// src/eslint/looks-of.ts
|
|
992
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
|
|
993
|
+
import { dirname as dirname2, join as join2, resolve as resolvePath } from "node:path";
|
|
994
|
+
import ts from "typescript";
|
|
995
|
+
var PLACEMENT2 = [
|
|
996
|
+
/^m[xytrblse]?-/,
|
|
997
|
+
/^p[xytrblse]?-/,
|
|
998
|
+
/^(?:w|h|size|min-w|min-h|max-w|max-h)-/,
|
|
999
|
+
/^(?:hidden|block|inline|inline-block|inline-flex|inline-grid|flex|grid|contents|flow-root)$/,
|
|
1000
|
+
/^(?:grow|shrink)$|^(?:flex|basis|grow|shrink|order|gap|gap-x|gap-y|space-x|space-y|col|row|grid-cols|grid-rows|grid-flow|auto-cols|auto-rows)-/,
|
|
1001
|
+
/^(?:self|justify|justify-items|justify-self|items|place-content|place-items|place-self)-|^content-(?!\[)/,
|
|
1002
|
+
/^(?:static|relative|absolute|fixed|sticky)$|^(?:inset|inset-x|inset-y|top|right|bottom|left|start|end|z)-/,
|
|
1003
|
+
/^(?:group|peer)(?:\/[\w-]+)?$/
|
|
1004
|
+
];
|
|
1005
|
+
var bare = (token) => token.replace(/^(?:[^:\s[\]]+:)+/, "");
|
|
1006
|
+
var isPlacementWord = (token) => PLACEMENT2.some((pattern) => pattern.test(bare(token)));
|
|
1007
|
+
var CLASSY = /^!?(?:[a-z0-9-]+:|\[[^\]\s]+\]:)*-?[a-z@][a-z0-9-]*(?:-\[[^\]\s]+\]|\/[\w.[\]-]+|-[\w.]+)*$|^\[[^\]\s]+\]$/;
|
|
1008
|
+
var words = (text) => text.split(/\s+/).filter(Boolean);
|
|
1009
|
+
var CLASS_FUNCTIONS2 = /* @__PURE__ */ new Set(["cn", "clsx", "cx", "twMerge", "classNames"]);
|
|
1010
|
+
var CLASS_PROP2 = /^(?:className|[a-z][A-Za-z]*ClassName)$/;
|
|
1011
|
+
function isClassList(text) {
|
|
1012
|
+
const list = words(text);
|
|
1013
|
+
return list.length >= 3 && list.every((w) => CLASSY.test(w)) && list.filter((w) => /[-:[]/.test(w)).length >= 2;
|
|
1014
|
+
}
|
|
1015
|
+
var CAP = 24;
|
|
1016
|
+
function classUnits(source, follow) {
|
|
1017
|
+
const consts = /* @__PURE__ */ new Map();
|
|
1018
|
+
const collect = (node) => {
|
|
1019
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isVariableDeclarationList(node.parent) && node.parent.flags & ts.NodeFlags.Const) {
|
|
1020
|
+
consts.set(node.name.text, node.initializer);
|
|
1021
|
+
}
|
|
1022
|
+
ts.forEachChild(node, collect);
|
|
1023
|
+
};
|
|
1024
|
+
collect(source);
|
|
1025
|
+
const product = (lists) => {
|
|
1026
|
+
let out = [[]];
|
|
1027
|
+
for (const alternatives of lists) {
|
|
1028
|
+
const next = [];
|
|
1029
|
+
for (const a of out) for (const b of alternatives) if (next.length < CAP) next.push([...a, ...b]);
|
|
1030
|
+
out = next;
|
|
1031
|
+
}
|
|
1032
|
+
return out;
|
|
1033
|
+
};
|
|
1034
|
+
const resolve3 = (node, depth = 0) => {
|
|
1035
|
+
if (!node || depth > 8) return [[]];
|
|
1036
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return [words(node.text)];
|
|
1037
|
+
if (ts.isTemplateExpression(node)) return product([[words(node.head.text)], ...node.templateSpans.flatMap((span) => [resolve3(span.expression, depth + 1), [words(span.literal.text)]])]);
|
|
1038
|
+
if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node) || ts.isNonNullExpression(node)) return resolve3(node.expression, depth + 1);
|
|
1039
|
+
if (ts.isJsxExpression(node)) return resolve3(node.expression, depth + 1);
|
|
1040
|
+
if (ts.isCallExpression(node)) return ts.isIdentifier(node.expression) && CLASS_FUNCTIONS2.has(node.expression.text) ? product(node.arguments.map((a) => resolve3(a, depth + 1))) : [[]];
|
|
1041
|
+
if (ts.isConditionalExpression(node)) return [...resolve3(node.whenTrue, depth + 1), ...resolve3(node.whenFalse, depth + 1)].slice(0, CAP);
|
|
1042
|
+
if (ts.isBinaryExpression(node)) {
|
|
1043
|
+
if (node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) return [[], ...resolve3(node.right, depth + 1)];
|
|
1044
|
+
if (node.operatorToken.kind === ts.SyntaxKind.BarBarToken || node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) return [...resolve3(node.left, depth + 1), ...resolve3(node.right, depth + 1)].slice(0, CAP);
|
|
1045
|
+
return [[]];
|
|
1046
|
+
}
|
|
1047
|
+
if (ts.isArrayLiteralExpression(node)) return product(node.elements.map((e) => resolve3(e, depth + 1)));
|
|
1048
|
+
if (ts.isObjectLiteralExpression(node)) return [[], node.properties.flatMap((p) => p.name && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) ? words(p.name.text) : [])];
|
|
1049
|
+
if (ts.isIdentifier(node)) {
|
|
1050
|
+
const init = consts.get(node.text) ?? follow?.(node.text);
|
|
1051
|
+
return init ? resolve3(init, depth + 1) : [[]];
|
|
1052
|
+
}
|
|
1053
|
+
if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
|
|
1054
|
+
let target = node.expression;
|
|
1055
|
+
if (ts.isIdentifier(target)) target = consts.get(target.text) ?? follow?.(target.text);
|
|
1056
|
+
while (target && (ts.isAsExpression(target) || ts.isSatisfiesExpression(target) || ts.isParenthesizedExpression(target))) target = target.expression;
|
|
1057
|
+
if (!target || !ts.isObjectLiteralExpression(target)) return [[]];
|
|
1058
|
+
const key = ts.isPropertyAccessExpression(node) ? node.name.text : ts.isStringLiteral(node.argumentExpression) ? node.argumentExpression.text : null;
|
|
1059
|
+
return target.properties.filter((p) => ts.isPropertyAssignment(p) && (key === null || !!p.name && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) && p.name.text === key)).flatMap((p) => resolve3(p.initializer, depth + 1)).slice(0, CAP);
|
|
1060
|
+
}
|
|
1061
|
+
return [[]];
|
|
1062
|
+
};
|
|
1063
|
+
const units = [];
|
|
1064
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1065
|
+
const sharedAt = (node) => {
|
|
1066
|
+
for (let at = node; at; at = at.parent) {
|
|
1067
|
+
if (ts.isVariableDeclaration(at)) return ts.isIdentifier(at.name) && /_CLASSES$/.test(at.name.text) && !!at.parent?.parent && ts.isVariableStatement(at.parent.parent) && !!at.parent.parent.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
1068
|
+
}
|
|
1069
|
+
return false;
|
|
1070
|
+
};
|
|
1071
|
+
const lineStart = (node) => {
|
|
1072
|
+
const line = source.getLineAndCharacterOfPosition(node.getStart(source)).line;
|
|
1073
|
+
let at = node;
|
|
1074
|
+
while (at.parent && !ts.isSourceFile(at.parent) && source.getLineAndCharacterOfPosition(at.parent.getStart(source)).line === line) at = at.parent;
|
|
1075
|
+
return at.getStart(source);
|
|
1076
|
+
};
|
|
1077
|
+
const add = (tokens, node, via, anchor = via === "literal" ? lineStart(node) : node.getStart(source)) => {
|
|
1078
|
+
const list = [...new Set(tokens.filter((t) => CLASSY.test(t)))];
|
|
1079
|
+
const look = list.filter((t) => !isPlacementWord(t));
|
|
1080
|
+
if (look.length === 0) return;
|
|
1081
|
+
const key = `${via}:${node.getStart(source)}:${[...list].sort().join(" ")}`;
|
|
1082
|
+
if (seen.has(key)) return;
|
|
1083
|
+
seen.add(key);
|
|
1084
|
+
units.push({ tokens: list, look, start: node.getStart(source), end: node.getEnd(), via, anchor, shared: via === "literal" && sharedAt(node) });
|
|
1085
|
+
};
|
|
1086
|
+
const visit = (node) => {
|
|
1087
|
+
if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && CLASS_PROP2.test(node.name.text) && node.initializer) {
|
|
1088
|
+
const element = node.parent.parent;
|
|
1089
|
+
for (const alternative of resolve3(node.initializer)) add(alternative, node, "attribute", element.getStart(source));
|
|
1090
|
+
}
|
|
1091
|
+
if ((ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) && isClassList(node.text)) add(words(node.text), node, "literal");
|
|
1092
|
+
ts.forEachChild(node, visit);
|
|
1093
|
+
};
|
|
1094
|
+
visit(source);
|
|
1095
|
+
return units;
|
|
1096
|
+
}
|
|
1097
|
+
function componentsOf(source) {
|
|
1098
|
+
const found = /* @__PURE__ */ new Map();
|
|
1099
|
+
const fn = (node) => {
|
|
1100
|
+
let current = node;
|
|
1101
|
+
while (current && ts.isCallExpression(current) && current.arguments.length) current = current.arguments[0];
|
|
1102
|
+
return current && (ts.isArrowFunction(current) || ts.isFunctionExpression(current)) ? current : void 0;
|
|
1103
|
+
};
|
|
1104
|
+
for (const statement2 of source.statements) {
|
|
1105
|
+
if (ts.isFunctionDeclaration(statement2) && statement2.name && /^[A-Z]/.test(statement2.name.text)) found.set(statement2.name.text, { start: statement2.getStart(source), end: statement2.getEnd() });
|
|
1106
|
+
if (ts.isVariableStatement(statement2)) {
|
|
1107
|
+
for (const declaration of statement2.declarationList.declarations) {
|
|
1108
|
+
const body = fn(declaration.initializer);
|
|
1109
|
+
if (ts.isIdentifier(declaration.name) && /^[A-Z]/.test(declaration.name.text) && body) found.set(declaration.name.text, { start: body.getStart(source), end: body.getEnd() });
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
return found;
|
|
1114
|
+
}
|
|
1115
|
+
var MIN_LOOK_WORDS = 4;
|
|
1116
|
+
function looksOf(source, names) {
|
|
1117
|
+
const components = componentsOf(source);
|
|
1118
|
+
const units = classUnits(source, importedConstants(source)).filter((u) => u.look.length >= MIN_LOOK_WORDS);
|
|
1119
|
+
const inside = (u, r) => u.start >= r.start && u.start < r.end;
|
|
1120
|
+
const out = /* @__PURE__ */ new Map();
|
|
1121
|
+
for (const name of names ?? components.keys()) {
|
|
1122
|
+
const range = components.get(name);
|
|
1123
|
+
const mine = units.filter((u) => (range ? inside(u, range) : false) || ![...components.values()].some((r) => inside(u, r)));
|
|
1124
|
+
out.set(name, [...new Set(mine.map((u) => u.look.join(" ")))]);
|
|
1125
|
+
}
|
|
1126
|
+
return out;
|
|
1127
|
+
}
|
|
1128
|
+
function parseForLooks(fileName2, text) {
|
|
1129
|
+
return ts.createSourceFile(fileName2, text, ts.ScriptTarget.Latest, true, fileName2.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
1130
|
+
}
|
|
1131
|
+
var MIN_SHARED = 4;
|
|
1132
|
+
var MIN_COVER = 0.8;
|
|
1133
|
+
function copies(list, look) {
|
|
1134
|
+
const mine = new Set(list);
|
|
1135
|
+
const shared = look.filter((word) => mine.has(word));
|
|
1136
|
+
if (shared.length < MIN_SHARED) return null;
|
|
1137
|
+
return shared.length / Math.min(list.length, look.length) >= MIN_COVER ? shared : null;
|
|
1138
|
+
}
|
|
1139
|
+
function importTarget(fromFile, specifier) {
|
|
1140
|
+
let base;
|
|
1141
|
+
if (specifier.startsWith(".")) base = resolvePath(dirname2(fromFile), specifier);
|
|
1142
|
+
else if (specifier.startsWith("@/")) {
|
|
1143
|
+
let dir = dirname2(resolvePath(fromFile));
|
|
1144
|
+
while (!existsSync3(join2(dir, "package.json")) && dirname2(dir) !== dir) dir = dirname2(dir);
|
|
1145
|
+
base = join2(dir, "src", specifier.slice(2));
|
|
1146
|
+
} else return null;
|
|
1147
|
+
for (const candidate of [`${base}.ts`, `${base}.tsx`, join2(base, "index.ts")]) if (existsSync3(candidate)) return candidate;
|
|
1148
|
+
return null;
|
|
1149
|
+
}
|
|
1150
|
+
var constantsCache = /* @__PURE__ */ new Map();
|
|
1151
|
+
function constantsOf(file) {
|
|
1152
|
+
const cached = constantsCache.get(file);
|
|
1153
|
+
if (cached) return cached;
|
|
1154
|
+
const found = /* @__PURE__ */ new Map();
|
|
1155
|
+
const source = parseForLooks(file, readFileSync2(file, "utf8"));
|
|
1156
|
+
for (const statement2 of source.statements) {
|
|
1157
|
+
if (!ts.isVariableStatement(statement2)) continue;
|
|
1158
|
+
for (const d of statement2.declarationList.declarations) if (ts.isIdentifier(d.name) && d.initializer) found.set(d.name.text, d.initializer);
|
|
1159
|
+
}
|
|
1160
|
+
constantsCache.set(file, found);
|
|
1161
|
+
return found;
|
|
1162
|
+
}
|
|
1163
|
+
function importedConstants(source) {
|
|
1164
|
+
const imported = /* @__PURE__ */ new Map();
|
|
1165
|
+
for (const statement2 of source.statements) {
|
|
1166
|
+
if (!ts.isImportDeclaration(statement2) || !ts.isStringLiteral(statement2.moduleSpecifier)) continue;
|
|
1167
|
+
const bindings = statement2.importClause?.namedBindings;
|
|
1168
|
+
if (!bindings || !ts.isNamedImports(bindings)) continue;
|
|
1169
|
+
const target = importTarget(source.fileName, statement2.moduleSpecifier.text);
|
|
1170
|
+
if (!target) continue;
|
|
1171
|
+
for (const element of bindings.elements) {
|
|
1172
|
+
const local = element.name.text;
|
|
1173
|
+
if (/_CLASSES$/.test(element.propertyName?.text ?? local)) imported.set(local, { file: target, name: element.propertyName?.text ?? local });
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return (name) => {
|
|
1177
|
+
const from = imported.get(name);
|
|
1178
|
+
if (!from) return void 0;
|
|
1179
|
+
try {
|
|
1180
|
+
return constantsOf(from.file).get(from.name);
|
|
1181
|
+
} catch {
|
|
1182
|
+
return void 0;
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
function sharedLooksOf(source) {
|
|
1187
|
+
const out = /* @__PURE__ */ new Map();
|
|
1188
|
+
const lookWords = (text) => words(text).filter((w) => CLASSY.test(w) && !isPlacementWord(w));
|
|
1189
|
+
for (const statement2 of source.statements) {
|
|
1190
|
+
if (!ts.isVariableStatement(statement2) || !statement2.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) continue;
|
|
1191
|
+
for (const d of statement2.declarationList.declarations) {
|
|
1192
|
+
if (!ts.isIdentifier(d.name) || !/_CLASSES$/.test(d.name.text) || !d.initializer) continue;
|
|
1193
|
+
let init = d.initializer;
|
|
1194
|
+
while (ts.isAsExpression(init) || ts.isSatisfiesExpression(init) || ts.isParenthesizedExpression(init)) init = init.expression;
|
|
1195
|
+
if (ts.isStringLiteral(init) || ts.isNoSubstitutionTemplateLiteral(init)) {
|
|
1196
|
+
const look = lookWords(init.text);
|
|
1197
|
+
if (look.length >= MIN_LOOK_WORDS) out.set(d.name.text, [look.join(" ")]);
|
|
1198
|
+
} else if (ts.isObjectLiteralExpression(init)) {
|
|
1199
|
+
for (const p of init.properties) {
|
|
1200
|
+
if (!ts.isPropertyAssignment(p) || !p.name || !(ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))) continue;
|
|
1201
|
+
if (!(ts.isStringLiteral(p.initializer) || ts.isNoSubstitutionTemplateLiteral(p.initializer))) continue;
|
|
1202
|
+
const look = lookWords(p.initializer.text);
|
|
1203
|
+
if (look.length >= MIN_LOOK_WORDS) out.set(`${d.name.text}.${p.name.text}`, [look.join(" ")]);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return out;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// src/eslint/no-copied-look.ts
|
|
1212
|
+
var packageDirCache2 = /* @__PURE__ */ new Map();
|
|
1213
|
+
function packageDirOf2(file) {
|
|
1214
|
+
const start = dirname3(resolve2(file));
|
|
1215
|
+
if (packageDirCache2.has(start)) return packageDirCache2.get(start) ?? null;
|
|
1216
|
+
let dir = start;
|
|
1217
|
+
for (; ; ) {
|
|
1218
|
+
if (existsSync4(join3(dir, "package.json"))) break;
|
|
1219
|
+
const up = dirname3(dir);
|
|
1220
|
+
if (up === dir) {
|
|
1221
|
+
dir = "";
|
|
1222
|
+
break;
|
|
1223
|
+
}
|
|
1224
|
+
dir = up;
|
|
1225
|
+
}
|
|
1226
|
+
packageDirCache2.set(start, dir || null);
|
|
1227
|
+
return dir || null;
|
|
1228
|
+
}
|
|
1229
|
+
var PACKAGE2 = "@estiva-app/ui";
|
|
1230
|
+
var nameOf3 = (dir) => {
|
|
1231
|
+
try {
|
|
1232
|
+
return JSON.parse(readFileSync3(join3(dir, "package.json"), "utf8")).name;
|
|
1233
|
+
} catch {
|
|
1234
|
+
return void 0;
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
var shippedCache = /* @__PURE__ */ new Map();
|
|
1238
|
+
function shippedLooks(appDir) {
|
|
1239
|
+
for (let dir = appDir; ; dir = dirname3(dir)) {
|
|
1240
|
+
const file = join3(dir, "node_modules", PACKAGE2, "registry.json");
|
|
1241
|
+
if (existsSync4(file)) {
|
|
1242
|
+
const mtime = statSync3(file).mtimeMs;
|
|
1243
|
+
const cached = shippedCache.get(file);
|
|
1244
|
+
if (cached && cached.mtime === mtime) return cached.looks;
|
|
1245
|
+
let looks = [];
|
|
1246
|
+
try {
|
|
1247
|
+
const registry = JSON.parse(readFileSync3(file, "utf8"));
|
|
1248
|
+
looks = registry.entries.flatMap((e) => (e.looks ?? []).map((l) => ({ part: e.name, file: e.sourceFile, look: l.split(" "), from: "package" })));
|
|
1249
|
+
} catch {
|
|
1250
|
+
}
|
|
1251
|
+
shippedCache.set(file, { mtime, looks });
|
|
1252
|
+
return looks;
|
|
1253
|
+
}
|
|
1254
|
+
if (dirname3(dir) === dir) return [];
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
function exportedNames(source) {
|
|
1258
|
+
const names = /* @__PURE__ */ new Set();
|
|
1259
|
+
const text = source.text;
|
|
1260
|
+
for (const m of text.matchAll(/export\s+(?:default\s+)?(?:function|const)\s+([A-Z]\w*)/g)) names.add(m[1]);
|
|
1261
|
+
for (const m of text.matchAll(/export\s*\{([^}]*)\}/g)) for (const part of m[1].split(",")) {
|
|
1262
|
+
const name = part.trim().split(/\s+as\s+/).pop();
|
|
1263
|
+
if (name && /^[A-Z]/.test(name)) names.add(name);
|
|
1264
|
+
}
|
|
1265
|
+
return names;
|
|
1266
|
+
}
|
|
1267
|
+
var ownCache = /* @__PURE__ */ new Map();
|
|
1268
|
+
function ownLooks(repoDir) {
|
|
1269
|
+
const out = [];
|
|
1270
|
+
const walk2 = (dir) => {
|
|
1271
|
+
let names = [];
|
|
1272
|
+
try {
|
|
1273
|
+
names = readdirSync(dir);
|
|
1274
|
+
} catch {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
for (const name of names) {
|
|
1278
|
+
if (name === "node_modules" || name === "dist") continue;
|
|
1279
|
+
const path = join3(dir, name);
|
|
1280
|
+
if (statSync3(path).isDirectory()) walk2(path);
|
|
1281
|
+
else if (/\.tsx?$/.test(name) && !/\.(?:stories|test)\.tsx?$/.test(name) && !name.endsWith(".d.ts")) {
|
|
1282
|
+
const mtime = statSync3(path).mtimeMs;
|
|
1283
|
+
let cached = ownCache.get(path);
|
|
1284
|
+
if (!cached || cached.mtime !== mtime) {
|
|
1285
|
+
const text = readFileSync3(path, "utf8");
|
|
1286
|
+
const source = parseForLooks(path, text);
|
|
1287
|
+
const exported = name.endsWith(".tsx") ? [...componentsOf(source).keys()].filter((n) => exportedNames(source).has(n)) : [];
|
|
1288
|
+
const file = relative(repoDir, path).replace(/\\/g, "/");
|
|
1289
|
+
const looks = [...looksOf(source, exported)].flatMap(([part, lists]) => lists.map((l) => ({ part, file, look: l.split(" "), from: "own" })));
|
|
1290
|
+
if (text.includes("_CLASSES")) for (const [part, lists] of sharedLooksOf(source)) for (const l of lists) looks.push({ part, file, look: l.split(" "), from: "shared" });
|
|
1291
|
+
cached = { mtime, looks };
|
|
1292
|
+
ownCache.set(path, cached);
|
|
1293
|
+
}
|
|
1294
|
+
out.push(...cached.looks);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
walk2(join3(repoDir, "src"));
|
|
1299
|
+
return out;
|
|
1300
|
+
}
|
|
1301
|
+
var noCopiedLook = {
|
|
1302
|
+
meta: {
|
|
1303
|
+
type: "suggestion",
|
|
1304
|
+
docs: { description: "A part's look typed again by hand, where the part (or a shared look) should be used" },
|
|
1305
|
+
schema: [],
|
|
1306
|
+
messages: {
|
|
1307
|
+
usePart: "This class list is `{{part}}`'s look, typed again ({{count}} words: {{words}}). Use `{{part}}` from {{from}}: a copy looks right today and stays behind the day `{{part}}` changes.",
|
|
1308
|
+
shareIt: "This look is also written in `{{part}}` ({{file}}): {{words}}. Write it once in src/looks.ts and use it in both, so the two cannot drift apart.",
|
|
1309
|
+
useShared: "This class list is `{{part}}` ({{file}}), typed again: {{words}}. Use `{{part}}`: a look written once cannot drift apart.",
|
|
1310
|
+
...ESCAPE_MESSAGES
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
create(context) {
|
|
1314
|
+
const file = context.filename;
|
|
1315
|
+
const repoDir = packageDirOf2(file);
|
|
1316
|
+
if (!repoDir) return {};
|
|
1317
|
+
const inPackage = nameOf3(repoDir) === PACKAGE2;
|
|
1318
|
+
return {
|
|
1319
|
+
Program() {
|
|
1320
|
+
const here = relative(repoDir, file).replace(/\\/g, "/");
|
|
1321
|
+
const source = parseForLooks(file, context.sourceCode.text);
|
|
1322
|
+
const declared = new Set(componentsOf(source).keys());
|
|
1323
|
+
const looks = [...inPackage ? [] : shippedLooks(repoDir), ...ownLooks(repoDir)].filter((l) => l.file !== here && !declared.has(l.part));
|
|
1324
|
+
if (!looks.length) return;
|
|
1325
|
+
const units = classUnits(source);
|
|
1326
|
+
const within = (u) => units.some((o) => o !== u && o.via === "attribute" && u.via === "literal" && u.start >= o.start && u.end <= o.end);
|
|
1327
|
+
const reported = /* @__PURE__ */ new Set();
|
|
1328
|
+
for (const unit of units) {
|
|
1329
|
+
if (within(unit) || reported.has(unit.start)) continue;
|
|
1330
|
+
let best = null;
|
|
1331
|
+
for (const look of looks) {
|
|
1332
|
+
if (unit.shared && look.from !== "package") continue;
|
|
1333
|
+
const shared = copies(unit.look, look.look);
|
|
1334
|
+
if (shared && (!best || shared.length > best.shared.length || shared.length === best.shared.length && look.from === "shared")) best = { look, shared };
|
|
1335
|
+
}
|
|
1336
|
+
if (!best) continue;
|
|
1337
|
+
const found = best;
|
|
1338
|
+
if (unit.via === "attribute" && units.some((o) => o.via === "literal" && !within(o) && found.shared.every((w) => o.look.includes(w)) && looks.some((l) => copies(o.look, l.look)))) continue;
|
|
1339
|
+
const loc = { start: context.sourceCode.getLocFromIndex(unit.start), end: context.sourceCode.getLocFromIndex(unit.end) };
|
|
1340
|
+
const at = { start: context.sourceCode.getLocFromIndex(unit.anchor), end: loc.end };
|
|
1341
|
+
if (isEscaped(context, { loc: at, range: [unit.anchor, unit.end] })) continue;
|
|
1342
|
+
reported.add(unit.start);
|
|
1343
|
+
const words2 = best.shared.map((w) => `\`${w}\``).join(" ");
|
|
1344
|
+
if (best.look.from === "shared") context.report({ loc, messageId: "useShared", data: { part: best.look.part, file: best.look.file, words: words2 } });
|
|
1345
|
+
else if (inPackage) context.report({ loc, messageId: "shareIt", data: { part: best.look.part, file: best.look.file, words: words2 } });
|
|
1346
|
+
else
|
|
1347
|
+
context.report({
|
|
1348
|
+
loc,
|
|
1349
|
+
messageId: "usePart",
|
|
1350
|
+
data: { part: best.look.part, count: String(best.shared.length), words: words2, from: best.look.from === "package" ? PACKAGE2 : `\`${best.look.file}\`` }
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
|
|
987
1358
|
// src/eslint/no-native-title.ts
|
|
988
1359
|
var PARTS_WITH_A_TITLE = ["CollapsibleSection", "ConfirmDialog", "ContainerHeader", "DialogShell", "ListColumn", "SectionHeader"];
|
|
989
1360
|
var child3 = (node, key) => node[key];
|
|
990
|
-
var
|
|
1361
|
+
var nameOf4 = (node) => node?.type === "JSXIdentifier" || node?.type === "Identifier" ? node.name : void 0;
|
|
991
1362
|
var noNativeTitle = {
|
|
992
1363
|
meta: {
|
|
993
1364
|
type: "problem",
|
|
@@ -1007,10 +1378,10 @@ var noNativeTitle = {
|
|
|
1007
1378
|
},
|
|
1008
1379
|
JSXAttribute(ruleNode) {
|
|
1009
1380
|
const attribute3 = ruleNode;
|
|
1010
|
-
if (
|
|
1381
|
+
if (nameOf4(child3(attribute3, "name")) !== "title") return;
|
|
1011
1382
|
const opening = attribute3.parent;
|
|
1012
1383
|
const tag = child3(opening, "name");
|
|
1013
|
-
const name =
|
|
1384
|
+
const name = nameOf4(tag);
|
|
1014
1385
|
if (!(name && /^[a-z]/.test(name))) {
|
|
1015
1386
|
const binding = locals && bindingOfTag(tag, locals);
|
|
1016
1387
|
if (!binding || binding.props !== "all") return;
|
|
@@ -1624,7 +1995,8 @@ var appRules = {
|
|
|
1624
1995
|
"no-restyled-part": noRestyledPart,
|
|
1625
1996
|
"no-handmade-header": noHandmadeHeader,
|
|
1626
1997
|
"no-handmade-empty-state": noHandmadeEmptyState,
|
|
1627
|
-
"no-native-title": noNativeTitle
|
|
1998
|
+
"no-native-title": noNativeTitle,
|
|
1999
|
+
"no-copied-look": noCopiedLook
|
|
1628
2000
|
};
|
|
1629
2001
|
var packageRules = {
|
|
1630
2002
|
"raw-element-outside-a-wrapper": rawElementOutsideAWrapper,
|
|
@@ -1634,7 +2006,8 @@ var packageRules = {
|
|
|
1634
2006
|
"no-restyled-part": noRestyledPart,
|
|
1635
2007
|
"no-handmade-header": noHandmadeHeader,
|
|
1636
2008
|
"no-handmade-empty-state": noHandmadeEmptyState,
|
|
1637
|
-
"no-native-title": noNativeTitle
|
|
2009
|
+
"no-native-title": noNativeTitle,
|
|
2010
|
+
"no-copied-look": noCopiedLook
|
|
1638
2011
|
};
|
|
1639
2012
|
var rules = { ...appRules, ...packageRules };
|
|
1640
2013
|
var plugin = {
|
|
@@ -1653,7 +2026,9 @@ plugin.configs.recommended = {
|
|
|
1653
2026
|
[`${PLUGIN_KEY}/no-restyled-part`]: "error",
|
|
1654
2027
|
[`${PLUGIN_KEY}/no-handmade-header`]: "error",
|
|
1655
2028
|
[`${PLUGIN_KEY}/no-handmade-empty-state`]: "error",
|
|
1656
|
-
[`${PLUGIN_KEY}/no-native-title`]: "error"
|
|
2029
|
+
[`${PLUGIN_KEY}/no-native-title`]: "error",
|
|
2030
|
+
// A warning, never an error (Katerina, 13 September): likeness is a judgement.
|
|
2031
|
+
[`${PLUGIN_KEY}/no-copied-look`]: "warn"
|
|
1657
2032
|
}
|
|
1658
2033
|
};
|
|
1659
2034
|
plugin.configs.strict = {
|
|
@@ -1664,7 +2039,8 @@ plugin.configs.strict = {
|
|
|
1664
2039
|
plugin.configs.package = {
|
|
1665
2040
|
name: "@estiva-app/ui/package",
|
|
1666
2041
|
plugins: { [PLUGIN_KEY]: plugin },
|
|
1667
|
-
|
|
2042
|
+
// Every inward rule blocks, but the copied look, which warns here too (UIG-25).
|
|
2043
|
+
rules: Object.fromEntries(PACKAGE_RULE_IDS.map((id) => [id, id === `${PLUGIN_KEY}/no-copied-look` ? "warn" : "error"]))
|
|
1668
2044
|
};
|
|
1669
2045
|
var index_default = plugin;
|
|
1670
2046
|
function countGates(results, seed = APP_RULE_IDS) {
|
|
@@ -1688,7 +2064,9 @@ function countGates(results, seed = APP_RULE_IDS) {
|
|
|
1688
2064
|
export {
|
|
1689
2065
|
APP_RULE_IDS,
|
|
1690
2066
|
ESCAPE_MARKER,
|
|
2067
|
+
MIN_COVER,
|
|
1691
2068
|
MIN_REASON,
|
|
2069
|
+
MIN_SHARED,
|
|
1692
2070
|
OWNED_BEHAVIOURS,
|
|
1693
2071
|
PACKAGE_RULE_IDS,
|
|
1694
2072
|
PARTS_WITH_A_TITLE,
|