@objectstack/lint 17.1.0 → 17.2.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/CHANGELOG.md +229 -0
- package/dist/index.cjs +236 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +218 -14
- package/dist/index.d.ts +218 -14
- package/dist/index.js +229 -31
- package/dist/index.js.map +1 -1
- package/dist/{runtime-s3X9D9hm.d.cts → runtime-cV-l_vtC.d.cts} +20 -1
- package/dist/{runtime-s3X9D9hm.d.ts → runtime-cV-l_vtC.d.ts} +20 -1
- package/dist/runtime.cjs +206 -28
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.cts +1 -1
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +206 -28
- package/dist/runtime.js.map +1 -1
- package/package.json +6 -5
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ACTION_BODY_SOURCE_UNPARSEABLE: () => ACTION_BODY_SOURCE_UNPARSEABLE,
|
|
23
24
|
ACTION_BODY_WRITE_EXCLUSIONS: () => ACTION_BODY_WRITE_EXCLUSIONS,
|
|
24
25
|
ACTION_BODY_WRITE_PATTERNS: () => ACTION_BODY_WRITE_PATTERNS,
|
|
25
26
|
ACTION_BODY_WRITE_PATTERN_IDS: () => ACTION_BODY_WRITE_PATTERN_IDS,
|
|
@@ -106,6 +107,7 @@ __export(index_exports, {
|
|
|
106
107
|
FLOW_WRITE_NODE_TYPES_DEFERRED: () => FLOW_WRITE_NODE_TYPES_DEFERRED,
|
|
107
108
|
FORM_COLSPAN_ABSOLUTE: () => FORM_COLSPAN_ABSOLUTE,
|
|
108
109
|
FORM_FIELD_UNKNOWN: () => FORM_FIELD_UNKNOWN,
|
|
110
|
+
HOOK_BODY_SOURCE_UNPARSEABLE: () => HOOK_BODY_SOURCE_UNPARSEABLE,
|
|
109
111
|
HOOK_BODY_WRITE_EXCLUSIONS: () => HOOK_BODY_WRITE_EXCLUSIONS,
|
|
110
112
|
HOOK_BODY_WRITE_PATTERNS: () => HOOK_BODY_WRITE_PATTERNS,
|
|
111
113
|
HOOK_BODY_WRITE_PATTERN_IDS: () => HOOK_BODY_WRITE_PATTERN_IDS,
|
|
@@ -129,6 +131,7 @@ __export(index_exports, {
|
|
|
129
131
|
PAGE_FIELD_UNKNOWN: () => PAGE_FIELD_UNKNOWN,
|
|
130
132
|
PAGE_FIELD_UNPROVISIONED: () => PAGE_FIELD_UNPROVISIONED,
|
|
131
133
|
PAGE_SOURCE_CLASSNAME: () => PAGE_SOURCE_CLASSNAME,
|
|
134
|
+
PARSE_FAILURE_HINT: () => PARSE_FAILURE_HINT,
|
|
132
135
|
PREDICATE_PATH_UNRESOLVED: () => PREDICATE_PATH_UNRESOLVED,
|
|
133
136
|
PREDICATE_PATH_UNROOTED: () => PREDICATE_PATH_UNROOTED,
|
|
134
137
|
PREDICATE_RHS_PATH_SHAPED: () => PREDICATE_RHS_PATH_SHAPED,
|
|
@@ -139,6 +142,7 @@ __export(index_exports, {
|
|
|
139
142
|
REACT_CHART_DRILLDOWN_INVALID: () => REACT_CHART_DRILLDOWN_INVALID,
|
|
140
143
|
REACT_CHART_FIELD_UNKNOWN: () => REACT_CHART_FIELD_UNKNOWN,
|
|
141
144
|
REACT_CHART_FIELD_UNPROVISIONED: () => REACT_CHART_FIELD_UNPROVISIONED,
|
|
145
|
+
REACT_PAGE_SOURCE_UNPARSEABLE: () => REACT_PAGE_SOURCE_UNPARSEABLE,
|
|
142
146
|
REFERENCE_INTEGRITY_RULES: () => REFERENCE_INTEGRITY_RULES,
|
|
143
147
|
RLS_PREDICATE_OVER_BUDGET: () => RLS_PREDICATE_OVER_BUDGET,
|
|
144
148
|
RLS_PREDICATE_UNENFORCEABLE: () => RLS_PREDICATE_UNENFORCEABLE,
|
|
@@ -170,8 +174,10 @@ __export(index_exports, {
|
|
|
170
174
|
SHARING_RULE_RUNTIME_VARIABLE_CONDITION: () => SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
|
|
171
175
|
SHARING_RULE_UNLOWERABLE_CONDITION: () => SHARING_RULE_UNLOWERABLE_CONDITION,
|
|
172
176
|
SORT_FIELD_UNKNOWN: () => SORT_FIELD_UNKNOWN,
|
|
177
|
+
SORT_FIELD_UNPROVISIONED: () => SORT_FIELD_UNPROVISIONED,
|
|
173
178
|
SORT_FIELD_UNSORTABLE: () => SORT_FIELD_UNSORTABLE,
|
|
174
179
|
STARTUP_OPEN_VOCABULARY_VERDICT: () => STARTUP_OPEN_VOCABULARY_VERDICT,
|
|
180
|
+
STARTUP_SOURCE_UNPARSEABLE: () => STARTUP_SOURCE_UNPARSEABLE,
|
|
175
181
|
STARTUP_VERDICT_ASSERTIVE_WORDING: () => STARTUP_VERDICT_ASSERTIVE_WORDING,
|
|
176
182
|
STARTUP_VERDICT_HINT: () => STARTUP_VERDICT_HINT,
|
|
177
183
|
STYLE_CLASSNAME_TAILWIND: () => STYLE_CLASSNAME_TAILWIND,
|
|
@@ -208,6 +214,7 @@ __export(index_exports, {
|
|
|
208
214
|
buildAccessMatrix: () => buildAccessMatrix,
|
|
209
215
|
buildRuntimeWriteSnapshots: () => buildRuntimeWriteSnapshots,
|
|
210
216
|
checkSortDeclaration: () => checkSortDeclaration,
|
|
217
|
+
describeParseFailure: () => describeParseFailure,
|
|
211
218
|
diffAccessMatrix: () => diffAccessMatrix,
|
|
212
219
|
extractHookBodyWriteSet: () => extractHookBodyWriteSet,
|
|
213
220
|
extractHookBodyWrites: () => extractHookBodyWrites,
|
|
@@ -1303,6 +1310,38 @@ function validateStackExpressions(stack) {
|
|
|
1303
1310
|
|
|
1304
1311
|
// src/lint-startup-registry-verdict.ts
|
|
1305
1312
|
var import_node_module = require("module");
|
|
1313
|
+
|
|
1314
|
+
// src/checked-parse.ts
|
|
1315
|
+
function createSourceFileChecked(tsc, fileName, source, options) {
|
|
1316
|
+
const sourceFile = tsc.createSourceFile(
|
|
1317
|
+
fileName,
|
|
1318
|
+
source,
|
|
1319
|
+
options.target,
|
|
1320
|
+
options.setParentNodes,
|
|
1321
|
+
options.scriptKind
|
|
1322
|
+
);
|
|
1323
|
+
const diagnostics = sourceFile.parseDiagnostics;
|
|
1324
|
+
if (!diagnostics || diagnostics.length === 0) return { sourceFile };
|
|
1325
|
+
const first = diagnostics[0];
|
|
1326
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(first.start ?? 0);
|
|
1327
|
+
const offset = options.synthesizedLinesBefore ?? 0;
|
|
1328
|
+
return {
|
|
1329
|
+
sourceFile,
|
|
1330
|
+
failure: {
|
|
1331
|
+
message: tsc.flattenDiagnosticMessageText(first.messageText, " "),
|
|
1332
|
+
line: Math.max(1, line + 1 - offset),
|
|
1333
|
+
column: character + 1,
|
|
1334
|
+
count: diagnostics.length
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
function describeParseFailure(failure) {
|
|
1339
|
+
const more = failure.count > 1 ? `; ${failure.count} syntax errors in total` : "";
|
|
1340
|
+
return `line ${failure.line}, column ${failure.column}: ${failure.message}${more}`;
|
|
1341
|
+
}
|
|
1342
|
+
var PARSE_FAILURE_HINT = "The source is parsed (never executed) so it can be checked. A source with syntax errors is only partially recovered, so the checks that follow may have skipped real problems \u2014 fix the syntax error and re-run to get a verdict that covers the whole source.";
|
|
1343
|
+
|
|
1344
|
+
// src/lint-startup-registry-verdict.ts
|
|
1306
1345
|
var import_meta = {};
|
|
1307
1346
|
var cachedTs = null;
|
|
1308
1347
|
function loadTypeScript() {
|
|
@@ -1319,6 +1358,7 @@ function loadTypeScript() {
|
|
|
1319
1358
|
}
|
|
1320
1359
|
var STARTUP_OPEN_VOCABULARY_VERDICT = "startup-open-vocabulary-verdict";
|
|
1321
1360
|
var STARTUP_VERDICT_ASSERTIVE_WORDING = "startup-verdict-assertive-wording";
|
|
1361
|
+
var STARTUP_SOURCE_UNPARSEABLE = "startup-source-unparseable";
|
|
1322
1362
|
var OPEN_VOCABULARY_PROBES = /* @__PURE__ */ new Map([
|
|
1323
1363
|
[
|
|
1324
1364
|
"getRegisteredNodeTypes",
|
|
@@ -1547,13 +1587,22 @@ function findStartupRegistryVerdicts(source, options = {}) {
|
|
|
1547
1587
|
if (!mentionsAny) return [];
|
|
1548
1588
|
const t = loadTypeScript();
|
|
1549
1589
|
const fileLabel = options.file ?? "source";
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
}
|
|
1590
|
+
const { sourceFile: sf, failure } = createSourceFileChecked(t, fileLabel, source, {
|
|
1591
|
+
target: t.ScriptTarget.Latest,
|
|
1592
|
+
setParentNodes: true,
|
|
1593
|
+
scriptKind: t.ScriptKind.TS
|
|
1594
|
+
});
|
|
1556
1595
|
const findings = [];
|
|
1596
|
+
if (failure) {
|
|
1597
|
+
findings.push({
|
|
1598
|
+
severity: "warning",
|
|
1599
|
+
rule: STARTUP_SOURCE_UNPARSEABLE,
|
|
1600
|
+
where: fileLabel,
|
|
1601
|
+
path: `${fileLabel}:${failure.line}`,
|
|
1602
|
+
message: `source did not parse (${describeParseFailure(failure)}), so this rule read a partially recovered tree \u2014 a startup verdict in the unread part is not reported.`,
|
|
1603
|
+
hint: PARSE_FAILURE_HINT
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1557
1606
|
const moduleBindings = moduleLevelMutableBindings(t, sf);
|
|
1558
1607
|
const functionBodies = indexFunctionBodies(t, sf);
|
|
1559
1608
|
const lineOf = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
@@ -3079,6 +3128,26 @@ function validateSearchableFields(stack) {
|
|
|
3079
3128
|
if (!isRec4(view)) continue;
|
|
3080
3129
|
const viewLabel2 = strName2(view.name) ?? strName2(view.objectName) ?? `#${vi}`;
|
|
3081
3130
|
const viewObject = strName2(view.objectName) ?? strName2(view.object);
|
|
3131
|
+
if (view.viewKind === "list" && !isRec4(view.config)) {
|
|
3132
|
+
check(
|
|
3133
|
+
view.searchableFields,
|
|
3134
|
+
listViewObject(view) ?? viewObject,
|
|
3135
|
+
`view "${viewLabel2}" (flattened list overlay)`,
|
|
3136
|
+
`views[${vi}].searchableFields`,
|
|
3137
|
+
"list-view searchableFields",
|
|
3138
|
+
"narrowing"
|
|
3139
|
+
);
|
|
3140
|
+
}
|
|
3141
|
+
if (view.viewKind === "list" && isRec4(view.config)) {
|
|
3142
|
+
check(
|
|
3143
|
+
view.config.searchableFields,
|
|
3144
|
+
listViewObject(view.config) ?? viewObject,
|
|
3145
|
+
`view "${viewLabel2}" (ViewItem record)`,
|
|
3146
|
+
`views[${vi}].config.searchableFields`,
|
|
3147
|
+
"list-view searchableFields",
|
|
3148
|
+
"narrowing"
|
|
3149
|
+
);
|
|
3150
|
+
}
|
|
3082
3151
|
if (isRec4(view.list)) {
|
|
3083
3152
|
check(
|
|
3084
3153
|
view.list.searchableFields,
|
|
@@ -3526,6 +3595,7 @@ function filterAttrValue(tsc, sf, attr) {
|
|
|
3526
3595
|
};
|
|
3527
3596
|
return perPosition(init.expression);
|
|
3528
3597
|
}
|
|
3598
|
+
var REACT_PAGE_SOURCE_UNPARSEABLE = "react-page-source-unparseable";
|
|
3529
3599
|
var REACT_CHART_FIELD_UNKNOWN = "react-chart-field-unknown";
|
|
3530
3600
|
var REACT_CHART_FIELD_UNPROVISIONED = "react-chart-field-unprovisioned";
|
|
3531
3601
|
var REACT_CHART_AGGREGATE_INVALID = "react-chart-aggregate-invalid";
|
|
@@ -3854,11 +3924,20 @@ function validateReactPageProps(stack) {
|
|
|
3854
3924
|
if (typeof source !== "string" || source.trim() === "") continue;
|
|
3855
3925
|
const name = String(page.name ?? `#${p}`);
|
|
3856
3926
|
const tsc = loadTypeScript2();
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3927
|
+
const { sourceFile: sf, failure } = createSourceFileChecked(tsc, "page.tsx", source, {
|
|
3928
|
+
target: tsc.ScriptTarget.Latest,
|
|
3929
|
+
setParentNodes: true,
|
|
3930
|
+
scriptKind: tsc.ScriptKind.TSX
|
|
3931
|
+
});
|
|
3932
|
+
if (failure) {
|
|
3933
|
+
findings.push({
|
|
3934
|
+
severity: "warning",
|
|
3935
|
+
rule: REACT_PAGE_SOURCE_UNPARSEABLE,
|
|
3936
|
+
where: `page "${name}"`,
|
|
3937
|
+
path: `pages[${p}].source`,
|
|
3938
|
+
message: `kind:'react' source did not parse (${describeParseFailure(failure)}), so the component-contract checks read a partially recovered tree and may have missed real problems.`,
|
|
3939
|
+
hint: PARSE_FAILURE_HINT
|
|
3940
|
+
});
|
|
3862
3941
|
}
|
|
3863
3942
|
const locals = localComponentNames(tsc, sf);
|
|
3864
3943
|
const visit = (node) => {
|
|
@@ -6741,6 +6820,7 @@ function validateNavObjectServability(stack) {
|
|
|
6741
6820
|
var import_data11 = require("@objectstack/spec/data");
|
|
6742
6821
|
var SORT_FIELD_UNKNOWN = "sort-field-unknown";
|
|
6743
6822
|
var SORT_FIELD_UNSORTABLE = "sort-field-unsortable";
|
|
6823
|
+
var SORT_FIELD_UNPROVISIONED = "sort-field-unprovisioned";
|
|
6744
6824
|
function isRec16(v) {
|
|
6745
6825
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
6746
6826
|
}
|
|
@@ -6803,7 +6883,7 @@ function distance3(a, b) {
|
|
|
6803
6883
|
}
|
|
6804
6884
|
return prev[n];
|
|
6805
6885
|
}
|
|
6806
|
-
function checkSortDeclaration(declared, objectName, fieldsByObject, where, path, subject) {
|
|
6886
|
+
function checkSortDeclaration(declared, objectName, fieldsByObject, where, path, subject, unprovisionedAnchors) {
|
|
6807
6887
|
const findings = [];
|
|
6808
6888
|
if (declared === void 0 || declared === null) return findings;
|
|
6809
6889
|
if (!objectName) return findings;
|
|
@@ -6811,10 +6891,23 @@ function checkSortDeclaration(declared, objectName, fieldsByObject, where, path,
|
|
|
6811
6891
|
const target = fieldsByObject.get(objectName);
|
|
6812
6892
|
if (!target) return findings;
|
|
6813
6893
|
const known = target.names;
|
|
6894
|
+
const anchors = unprovisionedAnchors?.get(objectName);
|
|
6814
6895
|
for (const key of readSortKeys(declared)) {
|
|
6815
6896
|
const name = key.field;
|
|
6816
6897
|
const head = name.split(".")[0];
|
|
6817
|
-
if (SYSTEM_FIELDS.has(head))
|
|
6898
|
+
if (SYSTEM_FIELDS.has(head)) {
|
|
6899
|
+
if (!name.includes(".") && anchors?.has(head)) {
|
|
6900
|
+
findings.push({
|
|
6901
|
+
severity: "warning",
|
|
6902
|
+
rule: SORT_FIELD_UNPROVISIONED,
|
|
6903
|
+
where,
|
|
6904
|
+
path: `${path}${key.at}`,
|
|
6905
|
+
message: `${subject} orders by "${name}", which resolves on object "${objectName}", but ${unprovisionedAnchorCause(objectName, name)} \u2014 so the ORDER BY reaches the driver, finds no column, and is dropped. Measured on a federated object over a real remote table, 'asc' and 'desc' return BYTE-IDENTICAL row order under a 200 while the same query on a real column reverses. Unlike this rule's other two verdicts nothing refuses it: the REST ingress (#6994) and the engine (#7095) both judge only 'formula', so the view's FIRST fetch \u2014 and every fetch after it \u2014 silently answers in an arbitrary order, which 'limit'/'offset' then slice into an arbitrary page.`,
|
|
6906
|
+
hint: unprovisionedAnchorHint(objectName, name)
|
|
6907
|
+
});
|
|
6908
|
+
}
|
|
6909
|
+
continue;
|
|
6910
|
+
}
|
|
6818
6911
|
if (!known.has(head)) {
|
|
6819
6912
|
const dotted = name.includes(".");
|
|
6820
6913
|
findings.push({
|
|
@@ -6847,9 +6940,18 @@ function validateSortableFields(stack) {
|
|
|
6847
6940
|
if (!isRec16(stack)) return findings;
|
|
6848
6941
|
const objects = Array.isArray(stack.objects) ? stack.objects : isRec16(stack.objects) ? Object.entries(stack.objects).map(([name, def]) => ({ name, ...def })) : [];
|
|
6849
6942
|
const fieldsByObject = indexObjectSearchTargets(stack);
|
|
6943
|
+
const unprovisionedAnchors = indexUnprovisionedAnchors(stack);
|
|
6850
6944
|
const check = (declared, objectName, where, path, subject) => {
|
|
6851
6945
|
findings.push(
|
|
6852
|
-
...checkSortDeclaration(
|
|
6946
|
+
...checkSortDeclaration(
|
|
6947
|
+
declared,
|
|
6948
|
+
objectName,
|
|
6949
|
+
fieldsByObject,
|
|
6950
|
+
where,
|
|
6951
|
+
path,
|
|
6952
|
+
subject,
|
|
6953
|
+
unprovisionedAnchors
|
|
6954
|
+
)
|
|
6853
6955
|
);
|
|
6854
6956
|
};
|
|
6855
6957
|
for (let oi = 0; oi < objects.length; oi++) {
|
|
@@ -6878,6 +6980,24 @@ function validateSortableFields(stack) {
|
|
|
6878
6980
|
if (!isRec16(view)) continue;
|
|
6879
6981
|
const viewLabel2 = strName11(view.name) ?? strName11(view.objectName) ?? `#${vi}`;
|
|
6880
6982
|
const viewObject = strName11(view.objectName) ?? strName11(view.object);
|
|
6983
|
+
if (view.viewKind === "list" && !isRec16(view.config)) {
|
|
6984
|
+
check(
|
|
6985
|
+
view.sort,
|
|
6986
|
+
listViewObject2(view) ?? viewObject,
|
|
6987
|
+
`view "${viewLabel2}" (flattened list overlay)`,
|
|
6988
|
+
`views[${vi}].sort`,
|
|
6989
|
+
"list-view sort"
|
|
6990
|
+
);
|
|
6991
|
+
}
|
|
6992
|
+
if (view.viewKind === "list" && isRec16(view.config)) {
|
|
6993
|
+
check(
|
|
6994
|
+
view.config.sort,
|
|
6995
|
+
listViewObject2(view.config) ?? viewObject,
|
|
6996
|
+
`view "${viewLabel2}" (ViewItem record)`,
|
|
6997
|
+
`views[${vi}].config.sort`,
|
|
6998
|
+
"list-view sort"
|
|
6999
|
+
);
|
|
7000
|
+
}
|
|
6881
7001
|
if (isRec16(view.list)) {
|
|
6882
7002
|
check(
|
|
6883
7003
|
view.list.sort,
|
|
@@ -8787,6 +8907,7 @@ function loadTypeScript3() {
|
|
|
8787
8907
|
return cachedTs3;
|
|
8788
8908
|
}
|
|
8789
8909
|
var HOOK_BODY_WRITE_UNKNOWN_FIELD = "hook-body-write-unknown-field";
|
|
8910
|
+
var HOOK_BODY_SOURCE_UNPARSEABLE = "hook-body-source-unparseable";
|
|
8790
8911
|
var HOOK_BODY_WRITE_UNPROVISIONED_ANCHOR = "hook-body-write-unprovisioned-anchor";
|
|
8791
8912
|
var HOOK_BODY_WRITE_PATTERNS = [
|
|
8792
8913
|
{
|
|
@@ -8900,15 +9021,18 @@ function extractHookBodyWriteSet(source) {
|
|
|
8900
9021
|
return { writes: [], ctxRecordEscapes: false };
|
|
8901
9022
|
}
|
|
8902
9023
|
const tsc = loadTypeScript3();
|
|
8903
|
-
const sf =
|
|
9024
|
+
const { sourceFile: sf, failure: parseFailure } = createSourceFileChecked(
|
|
9025
|
+
tsc,
|
|
8904
9026
|
"hook-body.ts",
|
|
8905
9027
|
`async function __body(ctx) {
|
|
8906
9028
|
${source}
|
|
8907
9029
|
}`,
|
|
8908
|
-
|
|
8909
|
-
|
|
8910
|
-
|
|
8911
|
-
|
|
9030
|
+
{
|
|
9031
|
+
target: tsc.ScriptTarget.Latest,
|
|
9032
|
+
setParentNodes: false,
|
|
9033
|
+
scriptKind: tsc.ScriptKind.TS,
|
|
9034
|
+
synthesizedLinesBefore: 1
|
|
9035
|
+
}
|
|
8912
9036
|
);
|
|
8913
9037
|
const writes = [];
|
|
8914
9038
|
const recordRefs = [];
|
|
@@ -9005,7 +9129,8 @@ ${source}
|
|
|
9005
9129
|
visit(sf);
|
|
9006
9130
|
return {
|
|
9007
9131
|
writes,
|
|
9008
|
-
ctxRecordEscapes: recordRefs.some((ref) => !consumedRecordRefs.has(ref))
|
|
9132
|
+
ctxRecordEscapes: recordRefs.some((ref) => !consumedRecordRefs.has(ref)),
|
|
9133
|
+
...parseFailure ? { parseFailure } : {}
|
|
9009
9134
|
};
|
|
9010
9135
|
}
|
|
9011
9136
|
function validateHookBodyWrites(stack) {
|
|
@@ -9019,11 +9144,22 @@ function validateHookBodyWrites(stack) {
|
|
|
9019
9144
|
if (!isRec23(body) || body.language !== "js") return;
|
|
9020
9145
|
const source = body.source;
|
|
9021
9146
|
if (typeof source !== "string" || source.trim() === "") return;
|
|
9022
|
-
const
|
|
9147
|
+
const extracted = extractHookBodyWriteSet(source);
|
|
9148
|
+
const hookName = typeof hook.name === "string" && hook.name ? hook.name : `#${hookIndex}`;
|
|
9149
|
+
if (extracted.parseFailure) {
|
|
9150
|
+
findings.push({
|
|
9151
|
+
severity: "warning",
|
|
9152
|
+
rule: HOOK_BODY_SOURCE_UNPARSEABLE,
|
|
9153
|
+
where: `hook "${hookName}" \u203A body`,
|
|
9154
|
+
path: `hooks[${hookIndex}].body.source`,
|
|
9155
|
+
message: `L2 body did not parse (${describeParseFailure(extracted.parseFailure)}), so its write set was read from a partially recovered tree \u2014 an undeclared field write in the unread part is not reported.`,
|
|
9156
|
+
hint: PARSE_FAILURE_HINT
|
|
9157
|
+
});
|
|
9158
|
+
}
|
|
9159
|
+
const writes = extracted.writes.filter((w) => HOOK_APPLICABLE_IDS.has(w.patternId));
|
|
9023
9160
|
if (writes.length === 0) return;
|
|
9024
9161
|
objectFields ?? (objectFields = indexObjectFields2(stack));
|
|
9025
9162
|
anchors ?? (anchors = indexUnprovisionedAnchors(stack));
|
|
9026
|
-
const hookName = typeof hook.name === "string" && hook.name ? hook.name : `#${hookIndex}`;
|
|
9027
9163
|
const targets = (Array.isArray(hook.object) ? hook.object : [hook.object]).filter(
|
|
9028
9164
|
(o) => typeof o === "string" && o.trim() !== ""
|
|
9029
9165
|
);
|
|
@@ -9108,6 +9244,7 @@ function fixHint(field, declared) {
|
|
|
9108
9244
|
var import_shared2 = require("@objectstack/spec/shared");
|
|
9109
9245
|
var ACTION_BODY_WRITE_UNKNOWN_FIELD = "action-body-write-unknown-field";
|
|
9110
9246
|
var ACTION_RECORD_WRITE_DISCARDED = "action-record-write-discarded";
|
|
9247
|
+
var ACTION_BODY_SOURCE_UNPARSEABLE = "action-body-source-unparseable";
|
|
9111
9248
|
var ACTION_BODY_WRITE_UNPROVISIONED_ANCHOR = "action-body-write-unprovisioned-anchor";
|
|
9112
9249
|
var ACTION_BODY_WRITE_PATTERN_IDS = ["api-crud-literal"];
|
|
9113
9250
|
var ACTION_RECORD_WRITE_PATTERN_IDS = ["record-property-assign"];
|
|
@@ -9175,11 +9312,21 @@ function validateActionBodyWrites(stack) {
|
|
|
9175
9312
|
let anchors = null;
|
|
9176
9313
|
for (const site of sites) {
|
|
9177
9314
|
if (!/\bapi\b/.test(site.source) && !/\brecord\b/.test(site.source)) continue;
|
|
9178
|
-
const { writes: allWrites, ctxRecordEscapes } = extractHookBodyWriteSet(site.source);
|
|
9315
|
+
const { writes: allWrites, ctxRecordEscapes, parseFailure } = extractHookBodyWriteSet(site.source);
|
|
9179
9316
|
const writes = allWrites.filter((w) => APPLICABLE_IDS.has(w.patternId));
|
|
9180
9317
|
const recordWrites = allWrites.filter((w) => RECORD_WRITE_IDS.has(w.patternId));
|
|
9181
|
-
if (writes.length === 0 && recordWrites.length === 0) continue;
|
|
9182
9318
|
const where = `action "${site.name}" \u203A body`;
|
|
9319
|
+
if (parseFailure) {
|
|
9320
|
+
findings.push({
|
|
9321
|
+
severity: "warning",
|
|
9322
|
+
rule: ACTION_BODY_SOURCE_UNPARSEABLE,
|
|
9323
|
+
where,
|
|
9324
|
+
path: site.path,
|
|
9325
|
+
message: `L2 body did not parse (${describeParseFailure(parseFailure)}), so its write set was read from a partially recovered tree \u2014 an undeclared field write in the unread part is not reported.`,
|
|
9326
|
+
hint: PARSE_FAILURE_HINT
|
|
9327
|
+
});
|
|
9328
|
+
}
|
|
9329
|
+
if (writes.length === 0 && recordWrites.length === 0) continue;
|
|
9183
9330
|
if (recordWrites.length > 0 && !ctxRecordEscapes) {
|
|
9184
9331
|
const reportedFields = /* @__PURE__ */ new Set();
|
|
9185
9332
|
for (const w of recordWrites) {
|
|
@@ -9320,16 +9467,26 @@ function fixHint3(field, declared) {
|
|
|
9320
9467
|
}
|
|
9321
9468
|
|
|
9322
9469
|
// src/reference-integrity-suite.ts
|
|
9470
|
+
var DEFAULT_MEMBER_RUNTIME_TYPES = ["flow"];
|
|
9323
9471
|
var REFERENCE_INTEGRITY_RULES = [
|
|
9324
9472
|
{ name: "validateObjectReferences", run: validateObjectReferences },
|
|
9325
|
-
|
|
9473
|
+
// [#9313] `runtimeTypes` gains `view` on this member and its sort sibling:
|
|
9474
|
+
// both judge a LIST VIEW's field references, and a standalone list view is
|
|
9475
|
+
// written through `PUT /api/v1/meta/view` — the only door a Studio tenant or
|
|
9476
|
+
// an MCP/AI author has. Their walks read the flattened overlay shape that
|
|
9477
|
+
// door carries (see each rule's `views[]` self rung), and they resolve only
|
|
9478
|
+
// against `stack.objects`, which the per-write snapshot DOES carry — so the
|
|
9479
|
+
// crossing has no missing-collection false-positive channel. Measured over
|
|
9480
|
+
// the shipped view corpus before crossing (0 refusals; population in the
|
|
9481
|
+
// #9313 PR).
|
|
9482
|
+
{ name: "validateSearchableFields", runtimeTypes: ["flow", "view"], run: validateSearchableFields },
|
|
9326
9483
|
// [#9257] The same reading, one axis over: a list view's `sort` is a field
|
|
9327
9484
|
// name written in metadata, resolved against the object's declared fields. It
|
|
9328
9485
|
// gates (`error`) because the runtime does not tolerate a bad one at all —
|
|
9329
9486
|
// `assertSortFieldsExist` (#6994) and `assertOrderByIsMaterializable` (#7095)
|
|
9330
9487
|
// both answer `400 INVALID_SORT` — and a view's sort is its FIRST fetch, so
|
|
9331
9488
|
// the refusal is the whole view, on every load, traced to nothing.
|
|
9332
|
-
{ name: "validateSortableFields", run: validateSortableFields },
|
|
9489
|
+
{ name: "validateSortableFields", runtimeTypes: ["flow", "view"], run: validateSortableFields },
|
|
9333
9490
|
{ name: "validateActionNameRefs", run: validateActionNameRefs },
|
|
9334
9491
|
{ name: "validatePageFieldBindings", run: validatePageFieldBindings },
|
|
9335
9492
|
{ name: "validateChartBindings", run: validateChartBindings },
|
|
@@ -9435,9 +9592,11 @@ var REFERENCE_INTEGRITY_RULES = [
|
|
|
9435
9592
|
// — only a page that is actually `kind:'react'` loads the compiler.
|
|
9436
9593
|
{ name: "validateReactPageProps", run: validateReactPageProps }
|
|
9437
9594
|
];
|
|
9438
|
-
function validateReferenceIntegrity(stack) {
|
|
9595
|
+
function validateReferenceIntegrity(stack, options) {
|
|
9439
9596
|
const findings = [];
|
|
9597
|
+
const writeType = options?.runtimeWriteType;
|
|
9440
9598
|
for (const rule of REFERENCE_INTEGRITY_RULES) {
|
|
9599
|
+
if (writeType !== void 0 && !(rule.runtimeTypes ?? DEFAULT_MEMBER_RUNTIME_TYPES).includes(writeType)) continue;
|
|
9441
9600
|
findings.push(...rule.run(stack));
|
|
9442
9601
|
}
|
|
9443
9602
|
return findings;
|
|
@@ -10826,9 +10985,31 @@ var AUTHORING_RULES = [
|
|
|
10826
10985
|
// collection in the snapshot and return nothing, and the two that would
|
|
10827
10986
|
// load `typescript` need a hook/action/react body the snapshot never
|
|
10828
10987
|
// carries — which is what `runtime-lazy-deps.test.ts` pins.
|
|
10988
|
+
//
|
|
10989
|
+
// [#9313] `view` joins, and the granularity decision #4463 P2 reserved is
|
|
10990
|
+
// taken HERE, in writing: the entry-level `runtimeTypes` says which WRITES
|
|
10991
|
+
// dispatch the suite, and the suite's own per-member `runtimeTypes`
|
|
10992
|
+
// (`ReferenceIntegrityRule`, default `['flow']`) says which MEMBERS judge
|
|
10993
|
+
// that snapshot — the gate passes the written type through
|
|
10994
|
+
// `ctx.runtimeWriteType`. A `view` write therefore reaches exactly the two
|
|
10995
|
+
// members that judge a list view's field references and resolve only
|
|
10996
|
+
// against the `objects` collection the snapshot carries
|
|
10997
|
+
// (`validateSearchableFields`, `validateSortableFields`). The whole suite
|
|
10998
|
+
// is NOT the right granularity for this door, measured not assumed, and
|
|
10999
|
+
// the crossing fails differently per body shape — both ways wrong:
|
|
11000
|
+
// `validateActionNameRefs` (error-tier) resolves `views[].list` /
|
|
11001
|
+
// `views[].listViews.*` action names against `stack.actions`, a collection
|
|
11002
|
+
// no per-write snapshot carries, so on a CONTAINER view write it would
|
|
11003
|
+
// refuse every stack-level action the body names (measured:
|
|
11004
|
+
// `action-name-undefined` on the snapshot shape, clean on the full stack)
|
|
11005
|
+
// — RUNTIME_NEEDS_FULL_SNAPSHOT's exact sentence, on the hottest write
|
|
11006
|
+
// type the gate has. On a FLATTENED overlay it has no rung at all, so the
|
|
11007
|
+
// crossing would be a silent no-op that reads as coverage — the very shape
|
|
11008
|
+
// #9313 was filed about. Flow snapshots are unchanged: every member keeps
|
|
11009
|
+
// the default `flow` declaration.
|
|
10829
11010
|
surfaces: CLI_AND_RUNTIME,
|
|
10830
|
-
runtimeTypes: ["flow"],
|
|
10831
|
-
run: (stack) => validateReferenceIntegrity(stack)
|
|
11011
|
+
runtimeTypes: ["flow", "view"],
|
|
11012
|
+
run: (stack, ctx) => validateReferenceIntegrity(stack, ctx)
|
|
10832
11013
|
},
|
|
10833
11014
|
// ADR-0078 / #5068 — the SDUI component-props gate. `PageComponent.properties`
|
|
10834
11015
|
// is `z.record(z.string(), z.unknown())` and ADR-0089 D3a strictness does not
|
|
@@ -11675,6 +11856,20 @@ function buildRuntimeWriteSnapshots(args) {
|
|
|
11675
11856
|
};
|
|
11676
11857
|
return { baseline, candidate };
|
|
11677
11858
|
}
|
|
11859
|
+
var NAME_KEYED_STACK_KEYS = ["objects", "permissions", "books"];
|
|
11860
|
+
var PATH_SAFE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
11861
|
+
var TOP_LEVEL_INDEX = /^(objects|permissions|books)\[(\d+)\](.*)$/;
|
|
11862
|
+
function nameKeyFindingPath(path, candidate) {
|
|
11863
|
+
const m = TOP_LEVEL_INDEX.exec(path);
|
|
11864
|
+
if (!m) return path;
|
|
11865
|
+
const [, stackKey, index, rest] = m;
|
|
11866
|
+
if (!NAME_KEYED_STACK_KEYS.includes(stackKey)) return path;
|
|
11867
|
+
const collection = candidate[stackKey];
|
|
11868
|
+
const entry = collection?.[Number(index)];
|
|
11869
|
+
const name = entry && typeof entry === "object" ? entry.name : void 0;
|
|
11870
|
+
if (typeof name !== "string" || !PATH_SAFE_NAME.test(name)) return path;
|
|
11871
|
+
return `${stackKey}.${name}${rest}`;
|
|
11872
|
+
}
|
|
11678
11873
|
function runRules(rules, stack, ctx) {
|
|
11679
11874
|
const findings = [];
|
|
11680
11875
|
for (const rule of rules) {
|
|
@@ -11704,9 +11899,12 @@ function runRuntimeAuthoringRules(args) {
|
|
|
11704
11899
|
...args.packageScope !== void 0 ? { packageScope: args.packageScope } : {}
|
|
11705
11900
|
});
|
|
11706
11901
|
if (!snapshots) return empty;
|
|
11707
|
-
const ctx = { sduiManifest: args.sduiManifest };
|
|
11902
|
+
const ctx = { sduiManifest: args.sduiManifest, runtimeWriteType: args.type };
|
|
11708
11903
|
const before = new Set(runRules(rules, snapshots.baseline, ctx).map(fingerprint));
|
|
11709
|
-
const added = runRules(rules, snapshots.candidate, ctx).filter((f) => !before.has(fingerprint(f)))
|
|
11904
|
+
const added = runRules(rules, snapshots.candidate, ctx).filter((f) => !before.has(fingerprint(f))).map((f) => {
|
|
11905
|
+
const path = nameKeyFindingPath(f.path, snapshots.candidate);
|
|
11906
|
+
return path === f.path ? f : { ...f, path };
|
|
11907
|
+
});
|
|
11710
11908
|
return {
|
|
11711
11909
|
errors: added.filter((f) => f.severity === "error"),
|
|
11712
11910
|
advisories: added.filter((f) => f.severity !== "error"),
|
|
@@ -11715,6 +11913,7 @@ function runRuntimeAuthoringRules(args) {
|
|
|
11715
11913
|
}
|
|
11716
11914
|
// Annotate the CommonJS export names for ESM import in node:
|
|
11717
11915
|
0 && (module.exports = {
|
|
11916
|
+
ACTION_BODY_SOURCE_UNPARSEABLE,
|
|
11718
11917
|
ACTION_BODY_WRITE_EXCLUSIONS,
|
|
11719
11918
|
ACTION_BODY_WRITE_PATTERNS,
|
|
11720
11919
|
ACTION_BODY_WRITE_PATTERN_IDS,
|
|
@@ -11801,6 +12000,7 @@ function runRuntimeAuthoringRules(args) {
|
|
|
11801
12000
|
FLOW_WRITE_NODE_TYPES_DEFERRED,
|
|
11802
12001
|
FORM_COLSPAN_ABSOLUTE,
|
|
11803
12002
|
FORM_FIELD_UNKNOWN,
|
|
12003
|
+
HOOK_BODY_SOURCE_UNPARSEABLE,
|
|
11804
12004
|
HOOK_BODY_WRITE_EXCLUSIONS,
|
|
11805
12005
|
HOOK_BODY_WRITE_PATTERNS,
|
|
11806
12006
|
HOOK_BODY_WRITE_PATTERN_IDS,
|
|
@@ -11824,6 +12024,7 @@ function runRuntimeAuthoringRules(args) {
|
|
|
11824
12024
|
PAGE_FIELD_UNKNOWN,
|
|
11825
12025
|
PAGE_FIELD_UNPROVISIONED,
|
|
11826
12026
|
PAGE_SOURCE_CLASSNAME,
|
|
12027
|
+
PARSE_FAILURE_HINT,
|
|
11827
12028
|
PREDICATE_PATH_UNRESOLVED,
|
|
11828
12029
|
PREDICATE_PATH_UNROOTED,
|
|
11829
12030
|
PREDICATE_RHS_PATH_SHAPED,
|
|
@@ -11834,6 +12035,7 @@ function runRuntimeAuthoringRules(args) {
|
|
|
11834
12035
|
REACT_CHART_DRILLDOWN_INVALID,
|
|
11835
12036
|
REACT_CHART_FIELD_UNKNOWN,
|
|
11836
12037
|
REACT_CHART_FIELD_UNPROVISIONED,
|
|
12038
|
+
REACT_PAGE_SOURCE_UNPARSEABLE,
|
|
11837
12039
|
REFERENCE_INTEGRITY_RULES,
|
|
11838
12040
|
RLS_PREDICATE_OVER_BUDGET,
|
|
11839
12041
|
RLS_PREDICATE_UNENFORCEABLE,
|
|
@@ -11865,8 +12067,10 @@ function runRuntimeAuthoringRules(args) {
|
|
|
11865
12067
|
SHARING_RULE_RUNTIME_VARIABLE_CONDITION,
|
|
11866
12068
|
SHARING_RULE_UNLOWERABLE_CONDITION,
|
|
11867
12069
|
SORT_FIELD_UNKNOWN,
|
|
12070
|
+
SORT_FIELD_UNPROVISIONED,
|
|
11868
12071
|
SORT_FIELD_UNSORTABLE,
|
|
11869
12072
|
STARTUP_OPEN_VOCABULARY_VERDICT,
|
|
12073
|
+
STARTUP_SOURCE_UNPARSEABLE,
|
|
11870
12074
|
STARTUP_VERDICT_ASSERTIVE_WORDING,
|
|
11871
12075
|
STARTUP_VERDICT_HINT,
|
|
11872
12076
|
STYLE_CLASSNAME_TAILWIND,
|
|
@@ -11903,6 +12107,7 @@ function runRuntimeAuthoringRules(args) {
|
|
|
11903
12107
|
buildAccessMatrix,
|
|
11904
12108
|
buildRuntimeWriteSnapshots,
|
|
11905
12109
|
checkSortDeclaration,
|
|
12110
|
+
describeParseFailure,
|
|
11906
12111
|
diffAccessMatrix,
|
|
11907
12112
|
extractHookBodyWriteSet,
|
|
11908
12113
|
extractHookBodyWrites,
|