@yuneta/gobj-ui 7.23.71 → 7.23.72

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.
@@ -25970,6 +25970,7 @@ function ac_paste_rows(gobj, event, kw, src) {
25970
25970
  * fkeys, times and numbers). Route by the form's mode.
25971
25971
  ************************************************************/
25972
25972
  function ac_form_save_record(gobj, event, kw, src) {
25973
+ let priv = gobj.priv;
25973
25974
  if (refuse_if_readonly$2(gobj, event)) return -1;
25974
25975
  let mode = (0, _yuneta_gobj_js.gobj_read_str_attr)(src, "form_mode");
25975
25976
  if (priv.reading_files) {
@@ -25965,6 +25965,7 @@ function ac_paste_rows(gobj, event, kw, src) {
25965
25965
  * fkeys, times and numbers). Route by the form's mode.
25966
25966
  ************************************************************/
25967
25967
  function ac_form_save_record(gobj, event, kw, src) {
25968
+ let priv = gobj.priv;
25968
25969
  if (refuse_if_readonly$2(gobj, event)) return -1;
25969
25970
  let mode = gobj_read_str_attr(src, "form_mode");
25970
25971
  if (priv.reading_files) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuneta/gobj-ui",
3
- "version": "7.23.71",
3
+ "version": "7.23.72",
4
4
  "type": "module",
5
5
  "main": "dist/gobj-ui.cjs.js",
6
6
  "module": "dist/gobj-ui.es.js",
@@ -3479,6 +3479,8 @@ function ac_paste_rows(gobj, event, kw, src)
3479
3479
  ************************************************************/
3480
3480
  function ac_form_save_record(gobj, event, kw, src)
3481
3481
  {
3482
+ let priv = gobj.priv;
3483
+
3482
3484
  if(refuse_if_readonly(gobj, event)) {
3483
3485
  return -1; /* Error already logged */
3484
3486
  }
@@ -0,0 +1,212 @@
1
+ /***********************************************************************
2
+ * priv_declared.test.js
3
+ *
4
+ * Every function that reads `priv` must have declared it.
5
+ *
6
+ * WHY THIS IS A TEST AND NOT A REVIEW HABIT: a missing
7
+ * `let priv = gobj.priv;` is a ReferenceError that throws only
8
+ * when its LINE runs, and the line that reads `priv` is usually
9
+ * the first line of a path nobody walks every day. One shipped in
10
+ * `ac_form_save_record()` (7.23.64, the "form is busy while it
11
+ * reads the picked files" guard) and it meant that for three
12
+ * releases NO record could be saved from a treedb form dialog, in
13
+ * any consumer — the record travelled, the action threw before
14
+ * writing it, and the dialog just stayed open.
15
+ *
16
+ * The rule is the one every gclass of this repo already follows:
17
+ * the function that uses `priv` opens with `let priv = gobj.priv;`,
18
+ * or takes it as an argument, or is nested inside one that did.
19
+ *
20
+ * Copyright (c) 2026, ArtGins.
21
+ * All Rights Reserved.
22
+ ***********************************************************************/
23
+ import {describe, it, expect} from "vitest";
24
+ import {readdirSync, readFileSync} from "node:fs";
25
+ import {dirname, join} from "node:path";
26
+ import {fileURLToPath} from "node:url";
27
+
28
+ const SRC = dirname(fileURLToPath(import.meta.url));
29
+
30
+
31
+ /***************************************************************
32
+ * The source with its comments blanked out, line count kept.
33
+ *
34
+ * A doc block that SHOWS the idiom (`priv.theme_observer = ...`)
35
+ * is not a use of it, and every early version of this check
36
+ * reported four of those before the one real bug.
37
+ ***************************************************************/
38
+ function without_comments(src)
39
+ {
40
+ let out = "";
41
+ let i = 0;
42
+ let state = "code"; /* code | line | block | s | d | t */
43
+
44
+ while(i < src.length) {
45
+ const c = src[i];
46
+ const d = src[i + 1];
47
+
48
+ if(state === "code") {
49
+ if(c === "/" && d === "/") {
50
+ state = "line";
51
+ i += 2;
52
+ continue;
53
+ }
54
+ if(c === "/" && d === "*") {
55
+ state = "block";
56
+ out += " ";
57
+ i += 2;
58
+ continue;
59
+ }
60
+ if(c === "'") {
61
+ state = "s";
62
+ } else if(c === '"') {
63
+ state = "d";
64
+ } else if(c === "`") {
65
+ state = "t";
66
+ }
67
+ out += c;
68
+ i++;
69
+ continue;
70
+ }
71
+
72
+ if(state === "line") {
73
+ if(c === "\n") {
74
+ state = "code";
75
+ out += c;
76
+ }
77
+ i++;
78
+ continue;
79
+ }
80
+
81
+ if(state === "block") {
82
+ if(c === "*" && d === "/") {
83
+ state = "code";
84
+ out += " ";
85
+ i += 2;
86
+ continue;
87
+ }
88
+ out += (c === "\n") ? "\n" : " ";
89
+ i++;
90
+ continue;
91
+ }
92
+
93
+ /* Inside a string: it ends at its own quote, and a backslash
94
+ * escapes whatever follows. */
95
+ if(c === "\\") {
96
+ out += " ";
97
+ i += 2;
98
+ continue;
99
+ }
100
+ if((state === "s" && c === "'") || (state === "d" && c === '"')
101
+ || (state === "t" && c === "`")) {
102
+ state = "code";
103
+ }
104
+ out += c;
105
+ i++;
106
+ }
107
+
108
+ return out;
109
+ }
110
+
111
+
112
+ /***************************************************************
113
+ * Every top-level function of a file, as {name, line, params,
114
+ * body}. A nested function belongs to the body of the one that
115
+ * holds it, which is what lets it use the parent's `priv`.
116
+ ***************************************************************/
117
+ function top_level_functions(src)
118
+ {
119
+ const lines = src.split("\n");
120
+ const starts = [];
121
+ for(let i = 0; i < lines.length; i++) {
122
+ if(/^(?:async )?function\s+\w+/.test(lines[i])) {
123
+ starts.push(i);
124
+ }
125
+ }
126
+ starts.push(lines.length);
127
+
128
+ const out = [];
129
+ for(let k = 0; k < starts.length - 1; k++) {
130
+ const a = starts[k];
131
+ const m = /^(?:async )?function\s+(\w+)\s*\(([^)]*)/.exec(lines[a]);
132
+ if(!m) {
133
+ continue;
134
+ }
135
+ out.push({
136
+ name: m[1],
137
+ line: a + 1,
138
+ params: m[2],
139
+ body: lines.slice(a, starts[k + 1]).join("\n"),
140
+ });
141
+ }
142
+ return out;
143
+ }
144
+
145
+
146
+ describe("every function that reads `priv` declares it", () => {
147
+
148
+ it("no bare `priv.` without `priv =` in the same function", () => {
149
+ const files = readdirSync(SRC)
150
+ .filter((f) => f.endsWith(".js") && !f.endsWith(".test.js"));
151
+ expect(files.length).toBeGreaterThan(10);
152
+
153
+ const offenders = [];
154
+ for(const f of files) {
155
+ const src = without_comments(readFileSync(join(SRC, f), "utf8"));
156
+ for(const fn of top_level_functions(src)) {
157
+ if(/(?<![.\w])priv(?![\w])/.test(fn.params)) {
158
+ continue; /* it takes priv as an argument */
159
+ }
160
+ const uses = /(?<![.\w])priv\s*\./.test(fn.body);
161
+ const declared = /(?<![.\w])priv\s*=[^=]/.test(fn.body);
162
+ if(uses && !declared) {
163
+ offenders.push(`${f}:${fn.line} ${fn.name}()`);
164
+ }
165
+ }
166
+ }
167
+
168
+ expect(offenders).toEqual([]);
169
+ });
170
+
171
+ it("the checker sees a missing declaration", () => {
172
+ /* The guard needs its own guard: a check that cannot fail is
173
+ * a check nobody can trust. */
174
+ const src = [
175
+ "function ac_broken(gobj, event, kw, src)",
176
+ "{",
177
+ " if(priv.reading_files) {",
178
+ " return -1;",
179
+ " }",
180
+ " return 0;",
181
+ "}",
182
+ ].join("\n");
183
+ const fns = top_level_functions(without_comments(src));
184
+ expect(fns.length).toBe(1);
185
+ expect(/(?<![.\w])priv\s*\./.test(fns[0].body)).toBe(true);
186
+ expect(/(?<![.\w])priv\s*=[^=]/.test(fns[0].body)).toBe(false);
187
+ });
188
+
189
+ it("a doc block that shows the idiom is not a use of it", () => {
190
+ const src = [
191
+ "/* priv.theme_observer = yui_watch_theme(gobj); */",
192
+ "function yui_is_dark()",
193
+ "{",
194
+ " return yui_theme_now() === 'dark';",
195
+ "}",
196
+ ].join("\n");
197
+ const fns = top_level_functions(without_comments(src));
198
+ expect(fns.length).toBe(1);
199
+ expect(/(?<![.\w])priv\s*\./.test(fns[0].body)).toBe(false);
200
+ });
201
+
202
+ it("`gobj.priv.x` is not a bare `priv`", () => {
203
+ const src = [
204
+ "function reads_through_gobj(gobj)",
205
+ "{",
206
+ " return gobj.priv.something;",
207
+ "}",
208
+ ].join("\n");
209
+ const fns = top_level_functions(without_comments(src));
210
+ expect(/(?<![.\w])priv\s*\./.test(fns[0].body)).toBe(false);
211
+ });
212
+ });