@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.
package/dist/gobj-ui.cjs.js
CHANGED
|
@@ -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) {
|
package/dist/gobj-ui.es.js
CHANGED
|
@@ -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
|
@@ -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
|
+
});
|