@objectstack/lint 11.10.0 → 12.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +159 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +90 -19
- package/dist/index.d.ts +90 -19
- package/dist/index.js +154 -16
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -24,6 +24,9 @@ __export(index_exports, {
|
|
|
24
24
|
CHART_FIELD_UNKNOWN: () => CHART_FIELD_UNKNOWN,
|
|
25
25
|
FIELD_GROUP_EMPTY: () => FIELD_GROUP_EMPTY,
|
|
26
26
|
FIELD_GROUP_UNDECLARED: () => FIELD_GROUP_UNDECLARED,
|
|
27
|
+
FORM_COLSPAN_ABSOLUTE: () => FORM_COLSPAN_ABSOLUTE,
|
|
28
|
+
FORM_FIELD_UNKNOWN: () => FORM_FIELD_UNKNOWN,
|
|
29
|
+
LIST_VIEW_FILTERS_IN_VIEWS_MODE: () => LIST_VIEW_FILTERS_IN_VIEWS_MODE,
|
|
27
30
|
MEASURE_AGGREGATE_INCOHERENT: () => MEASURE_AGGREGATE_INCOHERENT,
|
|
28
31
|
PAGE_SOURCE_CLASSNAME: () => PAGE_SOURCE_CLASSNAME,
|
|
29
32
|
SEMANTIC_ROLE_FIELD_UNKNOWN: () => SEMANTIC_ROLE_FIELD_UNKNOWN,
|
|
@@ -38,7 +41,9 @@ __export(index_exports, {
|
|
|
38
41
|
WIDGET_DATASET_UNKNOWN: () => WIDGET_DATASET_UNKNOWN,
|
|
39
42
|
WIDGET_DIMENSION_UNKNOWN: () => WIDGET_DIMENSION_UNKNOWN,
|
|
40
43
|
WIDGET_MEASURE_UNKNOWN: () => WIDGET_MEASURE_UNKNOWN,
|
|
44
|
+
validateFormLayout: () => validateFormLayout,
|
|
41
45
|
validateJsxPages: () => validateJsxPages,
|
|
46
|
+
validateListViewMode: () => validateListViewMode,
|
|
42
47
|
validatePageSourceStyling: () => validatePageSourceStyling,
|
|
43
48
|
validateReactPageProps: () => validateReactPageProps,
|
|
44
49
|
validateReactPages: () => validateReactPages,
|
|
@@ -423,6 +428,60 @@ function validateStackExpressions(stack) {
|
|
|
423
428
|
return issues;
|
|
424
429
|
}
|
|
425
430
|
|
|
431
|
+
// src/validate-list-view-mode.ts
|
|
432
|
+
var LIST_VIEW_FILTERS_IN_VIEWS_MODE = "list-view-filters-in-views-mode";
|
|
433
|
+
var FORBIDDEN_FIELDS = ["userFilters", "quickFilters"];
|
|
434
|
+
function asArray3(v) {
|
|
435
|
+
if (Array.isArray(v)) return v;
|
|
436
|
+
if (v && typeof v === "object") {
|
|
437
|
+
return Object.entries(v).map(([name, def]) => ({
|
|
438
|
+
name,
|
|
439
|
+
...def
|
|
440
|
+
}));
|
|
441
|
+
}
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
function scanView(view, where, path, out) {
|
|
445
|
+
if (!view || typeof view !== "object") return;
|
|
446
|
+
const rec = view;
|
|
447
|
+
for (const field of FORBIDDEN_FIELDS) {
|
|
448
|
+
if (rec[field] == null) continue;
|
|
449
|
+
out.push({
|
|
450
|
+
severity: "error",
|
|
451
|
+
rule: LIST_VIEW_FILTERS_IN_VIEWS_MODE,
|
|
452
|
+
where,
|
|
453
|
+
path: `${path}.${field}`,
|
|
454
|
+
message: `\`${field}\` is a page filters-mode control and is ignored on an object list view ("views" mode) \u2014 the ViewTabBar is the only nav control here.`,
|
|
455
|
+
hint: `Move \`${field}\` to a page list (InterfaceListPage, "filters" mode), or remove it. See ADR-0053.`
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function scanListViews(listViews, wherePrefix, pathPrefix, out) {
|
|
460
|
+
if (!listViews || typeof listViews !== "object") return;
|
|
461
|
+
for (const [name, view] of Object.entries(listViews)) {
|
|
462
|
+
scanView(
|
|
463
|
+
view,
|
|
464
|
+
`${wherePrefix} \u203A listViews.${name}`,
|
|
465
|
+
`${pathPrefix}.listViews.${name}`,
|
|
466
|
+
out
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function validateListViewMode(stack) {
|
|
471
|
+
const out = [];
|
|
472
|
+
asArray3(stack.objects).forEach((obj, i) => {
|
|
473
|
+
const label = typeof obj.name === "string" ? `object "${obj.name}"` : `objects[${i}]`;
|
|
474
|
+
scanListViews(obj.listViews, label, `objects[${i}]`, out);
|
|
475
|
+
});
|
|
476
|
+
asArray3(stack.views).forEach((view, i) => {
|
|
477
|
+
const named = typeof view.objectName === "string" ? view.objectName : typeof view.name === "string" ? view.name : void 0;
|
|
478
|
+
const label = named ? `view "${named}"` : `views[${i}]`;
|
|
479
|
+
scanView(view.list, `${label} \u203A list`, `views[${i}].list`, out);
|
|
480
|
+
scanListViews(view.listViews, label, `views[${i}]`, out);
|
|
481
|
+
});
|
|
482
|
+
return out;
|
|
483
|
+
}
|
|
484
|
+
|
|
426
485
|
// src/validate-responsive-styles.ts
|
|
427
486
|
var STYLE_NODE_MISSING_ID = "style-node-missing-id";
|
|
428
487
|
var STYLE_CLASSNAME_TAILWIND = "style-classname-tailwind";
|
|
@@ -626,7 +685,7 @@ function looksLikeTailwind(className) {
|
|
|
626
685
|
return false;
|
|
627
686
|
});
|
|
628
687
|
}
|
|
629
|
-
function
|
|
688
|
+
function asArray4(v) {
|
|
630
689
|
if (Array.isArray(v)) return v;
|
|
631
690
|
if (v && typeof v === "object") {
|
|
632
691
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -719,13 +778,13 @@ function checkNode(node, pageName, path, findings) {
|
|
|
719
778
|
}
|
|
720
779
|
function validateResponsiveStyles(stack) {
|
|
721
780
|
const findings = [];
|
|
722
|
-
const pages =
|
|
781
|
+
const pages = asArray4(stack.pages);
|
|
723
782
|
for (let p = 0; p < pages.length; p++) {
|
|
724
783
|
const page = pages[p];
|
|
725
784
|
const pageName = typeof page.name === "string" ? page.name : `pages[${p}]`;
|
|
726
|
-
const regions =
|
|
785
|
+
const regions = asArray4(page.regions);
|
|
727
786
|
for (let r = 0; r < regions.length; r++) {
|
|
728
|
-
const components =
|
|
787
|
+
const components = asArray4(regions[r].components);
|
|
729
788
|
for (let c = 0; c < components.length; c++) {
|
|
730
789
|
checkNode(components[c], pageName, `pages[${p}].regions[${r}].components[${c}]`, findings);
|
|
731
790
|
}
|
|
@@ -736,10 +795,10 @@ function validateResponsiveStyles(stack) {
|
|
|
736
795
|
|
|
737
796
|
// src/validate-jsx-pages.ts
|
|
738
797
|
var import_sdui_parser = require("@objectstack/sdui-parser");
|
|
739
|
-
var
|
|
798
|
+
var asArray5 = (v) => Array.isArray(v) ? v : [];
|
|
740
799
|
function validateJsxPages(stack, opts = {}) {
|
|
741
800
|
const findings = [];
|
|
742
|
-
const pages =
|
|
801
|
+
const pages = asArray5(stack.pages);
|
|
743
802
|
for (let p = 0; p < pages.length; p++) {
|
|
744
803
|
const page = pages[p];
|
|
745
804
|
if (!page || page.kind !== "html" && page.kind !== "jsx") continue;
|
|
@@ -787,10 +846,10 @@ function loadSucraseTransform() {
|
|
|
787
846
|
}
|
|
788
847
|
return cachedTransform;
|
|
789
848
|
}
|
|
790
|
-
var
|
|
849
|
+
var asArray6 = (v) => Array.isArray(v) ? v : [];
|
|
791
850
|
function validateReactPages(stack) {
|
|
792
851
|
const findings = [];
|
|
793
|
-
const pages =
|
|
852
|
+
const pages = asArray6(stack.pages);
|
|
794
853
|
for (let p = 0; p < pages.length; p++) {
|
|
795
854
|
const page = pages[p];
|
|
796
855
|
if (!page || page.kind !== "react") continue;
|
|
@@ -842,7 +901,7 @@ function loadTypeScript() {
|
|
|
842
901
|
}
|
|
843
902
|
return cachedTs;
|
|
844
903
|
}
|
|
845
|
-
var
|
|
904
|
+
var asArray7 = (v) => Array.isArray(v) ? v : [];
|
|
846
905
|
var BLOCKS = new Map(
|
|
847
906
|
import_ui.REACT_BLOCKS.map((b) => [
|
|
848
907
|
b.tag,
|
|
@@ -881,7 +940,7 @@ function nearestKnown(prop, known) {
|
|
|
881
940
|
}
|
|
882
941
|
function validateReactPageProps(stack) {
|
|
883
942
|
const findings = [];
|
|
884
|
-
const pages =
|
|
943
|
+
const pages = asArray7(stack.pages);
|
|
885
944
|
for (let p = 0; p < pages.length; p++) {
|
|
886
945
|
const page = pages[p];
|
|
887
946
|
if (!page || page.kind !== "react") continue;
|
|
@@ -949,11 +1008,11 @@ function validateReactPageProps(stack) {
|
|
|
949
1008
|
|
|
950
1009
|
// src/validate-page-source-styling.ts
|
|
951
1010
|
var PAGE_SOURCE_CLASSNAME = "page-source-className-tailwind";
|
|
952
|
-
var
|
|
1011
|
+
var asArray8 = (v) => Array.isArray(v) ? v : [];
|
|
953
1012
|
var CLASSNAME_ATTR = /\bclassName\s*=\s*["'{]/g;
|
|
954
1013
|
function validatePageSourceStyling(stack) {
|
|
955
1014
|
const findings = [];
|
|
956
|
-
const pages =
|
|
1015
|
+
const pages = asArray8(stack.pages);
|
|
957
1016
|
for (let p = 0; p < pages.length; p++) {
|
|
958
1017
|
const page = pages[p];
|
|
959
1018
|
if (!page) continue;
|
|
@@ -982,7 +1041,7 @@ function validatePageSourceStyling(stack) {
|
|
|
982
1041
|
var import_data2 = require("@objectstack/spec/data");
|
|
983
1042
|
var TITLE_FORMAT_RETIRED = "title-format-retired";
|
|
984
1043
|
var TITLE_UNRESOLVABLE = "title-unresolvable";
|
|
985
|
-
function
|
|
1044
|
+
function asArray9(v) {
|
|
986
1045
|
if (Array.isArray(v)) return v;
|
|
987
1046
|
if (v && typeof v === "object") {
|
|
988
1047
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -991,7 +1050,7 @@ function asArray8(v) {
|
|
|
991
1050
|
}
|
|
992
1051
|
function validateRecordTitle(stack) {
|
|
993
1052
|
const findings = [];
|
|
994
|
-
const objects =
|
|
1053
|
+
const objects = asArray9(stack.objects);
|
|
995
1054
|
for (let i = 0; i < objects.length; i++) {
|
|
996
1055
|
const obj = objects[i];
|
|
997
1056
|
const objName = typeof obj.name === "string" ? obj.name : `(object ${i})`;
|
|
@@ -1026,7 +1085,7 @@ function validateRecordTitle(stack) {
|
|
|
1026
1085
|
var FIELD_GROUP_UNDECLARED = "field-group-undeclared";
|
|
1027
1086
|
var FIELD_GROUP_EMPTY = "field-group-empty";
|
|
1028
1087
|
var SEMANTIC_ROLE_FIELD_UNKNOWN = "semantic-role-field-unknown";
|
|
1029
|
-
function
|
|
1088
|
+
function asArray10(v) {
|
|
1030
1089
|
if (Array.isArray(v)) return v;
|
|
1031
1090
|
if (v && typeof v === "object") {
|
|
1032
1091
|
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
@@ -1035,7 +1094,7 @@ function asArray9(v) {
|
|
|
1035
1094
|
}
|
|
1036
1095
|
function validateSemanticRoles(stack) {
|
|
1037
1096
|
const findings = [];
|
|
1038
|
-
const objects =
|
|
1097
|
+
const objects = asArray10(stack.objects);
|
|
1039
1098
|
for (let i = 0; i < objects.length; i++) {
|
|
1040
1099
|
const obj = objects[i];
|
|
1041
1100
|
if (!obj || typeof obj !== "object") continue;
|
|
@@ -1101,12 +1160,94 @@ function validateSemanticRoles(stack) {
|
|
|
1101
1160
|
}
|
|
1102
1161
|
return findings;
|
|
1103
1162
|
}
|
|
1163
|
+
|
|
1164
|
+
// src/validate-form-layout.ts
|
|
1165
|
+
var FORM_FIELD_UNKNOWN = "form-field-unknown";
|
|
1166
|
+
var FORM_COLSPAN_ABSOLUTE = "absolute-colspan-discouraged";
|
|
1167
|
+
function asArray11(v) {
|
|
1168
|
+
if (Array.isArray(v)) return v;
|
|
1169
|
+
if (v && typeof v === "object") {
|
|
1170
|
+
return Object.entries(v).map(([name, def]) => ({ name, ...def }));
|
|
1171
|
+
}
|
|
1172
|
+
return [];
|
|
1173
|
+
}
|
|
1174
|
+
function fieldNameOf(entry) {
|
|
1175
|
+
if (typeof entry === "string") return entry.length > 0 ? entry : null;
|
|
1176
|
+
if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
1177
|
+
const f = entry.field;
|
|
1178
|
+
return typeof f === "string" && f.length > 0 ? f : null;
|
|
1179
|
+
}
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
function boundObject(view) {
|
|
1183
|
+
const data = view.data;
|
|
1184
|
+
if (data && typeof data === "object" && typeof data.object === "string") {
|
|
1185
|
+
return data.object;
|
|
1186
|
+
}
|
|
1187
|
+
return typeof view.objectName === "string" ? view.objectName : void 0;
|
|
1188
|
+
}
|
|
1189
|
+
function validateFormLayout(stack) {
|
|
1190
|
+
const findings = [];
|
|
1191
|
+
const objectFields = /* @__PURE__ */ new Map();
|
|
1192
|
+
for (const obj of asArray11(stack.objects)) {
|
|
1193
|
+
const name = typeof obj.name === "string" ? obj.name : void 0;
|
|
1194
|
+
if (!name) continue;
|
|
1195
|
+
const fields = obj.fields && typeof obj.fields === "object" && !Array.isArray(obj.fields) ? Object.keys(obj.fields) : [];
|
|
1196
|
+
objectFields.set(name, new Set(fields));
|
|
1197
|
+
}
|
|
1198
|
+
const views = asArray11(stack.views);
|
|
1199
|
+
for (let i = 0; i < views.length; i++) {
|
|
1200
|
+
const view = views[i];
|
|
1201
|
+
if (!view || typeof view !== "object") continue;
|
|
1202
|
+
const sections = Array.isArray(view.sections) ? view.sections : null;
|
|
1203
|
+
if (!sections) continue;
|
|
1204
|
+
const viewName = typeof view.name === "string" ? view.name : `(view ${i})`;
|
|
1205
|
+
const objName = boundObject(view);
|
|
1206
|
+
const known = objName ? objectFields.get(objName) : void 0;
|
|
1207
|
+
const where = `view "${viewName}"`;
|
|
1208
|
+
const base = `views[${i}]`;
|
|
1209
|
+
for (let s = 0; s < sections.length; s++) {
|
|
1210
|
+
const sec = sections[s];
|
|
1211
|
+
const secFields = sec && typeof sec === "object" && Array.isArray(sec.fields) ? sec.fields : [];
|
|
1212
|
+
for (let f = 0; f < secFields.length; f++) {
|
|
1213
|
+
const entry = secFields[f];
|
|
1214
|
+
const fname = fieldNameOf(entry);
|
|
1215
|
+
const fpath = `${base}.sections[${s}].fields[${f}]`;
|
|
1216
|
+
if (fname && known && !known.has(fname)) {
|
|
1217
|
+
findings.push({
|
|
1218
|
+
severity: "warning",
|
|
1219
|
+
rule: FORM_FIELD_UNKNOWN,
|
|
1220
|
+
where,
|
|
1221
|
+
path: fpath,
|
|
1222
|
+
message: `${viewName}: field "${fname}" is not a field on object "${objName}" \u2014 it is silently skipped and never renders on the form`,
|
|
1223
|
+
hint: `Fix the field name, or add "${fname}" to ${objName}. Section field references must match the object's field names exactly.`
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
const colSpan = entry && typeof entry === "object" && !Array.isArray(entry) ? entry.colSpan : void 0;
|
|
1227
|
+
if (colSpan != null) {
|
|
1228
|
+
findings.push({
|
|
1229
|
+
severity: "warning",
|
|
1230
|
+
rule: FORM_COLSPAN_ABSOLUTE,
|
|
1231
|
+
where,
|
|
1232
|
+
path: `${fpath}.colSpan`,
|
|
1233
|
+
message: `${viewName}: field "${fname ?? "?"}" sets absolute colSpan ${String(colSpan)} \u2014 the form's column count is derived per surface (mobile 1 / modal 2 / page 3-4), so a fixed span only aligns at one width`,
|
|
1234
|
+
hint: `Prefer span: 'full' (whole row at any column count), or omit for auto width. The renderer clamps colSpan to the current column count.`
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return findings;
|
|
1241
|
+
}
|
|
1104
1242
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1105
1243
|
0 && (module.exports = {
|
|
1106
1244
|
CHART_CONFIG_MISSING,
|
|
1107
1245
|
CHART_FIELD_UNKNOWN,
|
|
1108
1246
|
FIELD_GROUP_EMPTY,
|
|
1109
1247
|
FIELD_GROUP_UNDECLARED,
|
|
1248
|
+
FORM_COLSPAN_ABSOLUTE,
|
|
1249
|
+
FORM_FIELD_UNKNOWN,
|
|
1250
|
+
LIST_VIEW_FILTERS_IN_VIEWS_MODE,
|
|
1110
1251
|
MEASURE_AGGREGATE_INCOHERENT,
|
|
1111
1252
|
PAGE_SOURCE_CLASSNAME,
|
|
1112
1253
|
SEMANTIC_ROLE_FIELD_UNKNOWN,
|
|
@@ -1121,7 +1262,9 @@ function validateSemanticRoles(stack) {
|
|
|
1121
1262
|
WIDGET_DATASET_UNKNOWN,
|
|
1122
1263
|
WIDGET_DIMENSION_UNKNOWN,
|
|
1123
1264
|
WIDGET_MEASURE_UNKNOWN,
|
|
1265
|
+
validateFormLayout,
|
|
1124
1266
|
validateJsxPages,
|
|
1267
|
+
validateListViewMode,
|
|
1125
1268
|
validatePageSourceStyling,
|
|
1126
1269
|
validateReactPageProps,
|
|
1127
1270
|
validateReactPages,
|