@wcstack/state 2.5.0 → 2.6.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.ja.md +60 -1
- package/README.md +60 -1
- package/dist/auto.min.js +1 -1
- package/dist/auto.min.js.map +1 -1
- package/dist/index.d.ts +21 -4
- package/dist/index.esm.js +5725 -5083
- package/dist/index.esm.js.map +1 -1
- package/dist/manifest.esm.js +25 -2
- package/dist/parser.d.ts +21 -1
- package/dist/parser.esm.js +119 -3
- package/package.json +1 -1
package/dist/manifest.esm.js
CHANGED
|
@@ -81,6 +81,16 @@ function valueMustBeArray(fnName) {
|
|
|
81
81
|
raiseError(`filter ${fnName} requires an array value`);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
const warned = new Set();
|
|
85
|
+
/** 同じ文面は 1 回だけ警告する(文面は書き方とサイトを含むので、書き方・サイトごとに 1 回) */
|
|
86
|
+
function warnV3Migration(message) {
|
|
87
|
+
if (warned.has(message)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
warned.add(message);
|
|
91
|
+
console.warn(`[@wcstack/state] [wcs/v3-migration] ${message} See "Preparing for 3.0" in the @wcstack/state README.`);
|
|
92
|
+
}
|
|
93
|
+
|
|
84
94
|
/**
|
|
85
95
|
* builtinFilters.ts
|
|
86
96
|
*
|
|
@@ -807,6 +817,12 @@ const hms = (options) => {
|
|
|
807
817
|
return `${hours}${opt}${minutes}${opt}${seconds}`;
|
|
808
818
|
};
|
|
809
819
|
};
|
|
820
|
+
/** 3.0 は truthy / falsy / defaults を JavaScript の真偽判定に揃える(要件 B10)— 0n の差を予告する */
|
|
821
|
+
function warnBigIntZero(fnName, value) {
|
|
822
|
+
if (value === 0n) {
|
|
823
|
+
warnV3Migration(`"${fnName}" got 0n: 3.0 treats it as falsy.`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
810
826
|
/**
|
|
811
827
|
* Falsy filter - checks if value is falsy.
|
|
812
828
|
*
|
|
@@ -814,7 +830,10 @@ const hms = (options) => {
|
|
|
814
830
|
* @returns Filter function that returns true for false/null/undefined/0/''/NaN
|
|
815
831
|
*/
|
|
816
832
|
const falsy = (_options) => {
|
|
817
|
-
return (value) =>
|
|
833
|
+
return (value) => {
|
|
834
|
+
warnBigIntZero('falsy', value);
|
|
835
|
+
return value === false || value === null || value === undefined || value === 0 || value === '' || Number.isNaN(value);
|
|
836
|
+
};
|
|
818
837
|
};
|
|
819
838
|
/**
|
|
820
839
|
* Truthy filter - checks if value is truthy.
|
|
@@ -823,7 +842,10 @@ const falsy = (_options) => {
|
|
|
823
842
|
* @returns Filter function that returns true for non-falsy values
|
|
824
843
|
*/
|
|
825
844
|
const truthy = (_options) => {
|
|
826
|
-
return (value) =>
|
|
845
|
+
return (value) => {
|
|
846
|
+
warnBigIntZero('truthy', value);
|
|
847
|
+
return value !== false && value !== null && value !== undefined && value !== 0 && value !== '' && !Number.isNaN(value);
|
|
848
|
+
};
|
|
827
849
|
};
|
|
828
850
|
/**
|
|
829
851
|
* Default filter - returns default value if input is falsy.
|
|
@@ -834,6 +856,7 @@ const truthy = (_options) => {
|
|
|
834
856
|
const defaults = (options) => {
|
|
835
857
|
const opt = options?.[0] ?? optionsRequired('defaults');
|
|
836
858
|
return (value) => {
|
|
859
|
+
warnBigIntZero('defaults', value);
|
|
837
860
|
if (value === false || value === null || value === undefined || value === 0 || value === '' || Number.isNaN(value)) {
|
|
838
861
|
return opt;
|
|
839
862
|
}
|
package/dist/parser.d.ts
CHANGED
|
@@ -71,6 +71,26 @@ declare function parseBindTextForEmbeddedNode(bindText: string): ParseBindTextRe
|
|
|
71
71
|
|
|
72
72
|
declare function getPathInfo(path: string): IPathInfo;
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* v3MigrationRules.ts — 3.0 で拒否される(または読み方が変わる)書き方の判定。純関数。
|
|
76
|
+
*
|
|
77
|
+
* ランタイム(v3Migration.ts が `[wcs/v3-migration]` として console に 1 回ずつ出す)と
|
|
78
|
+
* tooling(`@wcstack/state/parser` 経由で lint が同じ判定を使う)の共通の正本。
|
|
79
|
+
* ここは console に何も出さず、2.x の挙動も変えない(次期メジャーの要件 D2)。
|
|
80
|
+
* 文面は短く保つ — 全文はランタイムのバンドルに載る。2.x との差と書き換え先の一覧は
|
|
81
|
+
* state README の "Preparing for 3.0"(警告の末尾が案内する)。
|
|
82
|
+
*
|
|
83
|
+
* 式は 2.x の分割(`;` で無条件に割る)のまま受け取る — 引用符の中の `;` は 2.x では既に壊れている。
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* `data-wcs` の式 1 つ(`;` を含まない、trim 済み)の判定。`parsed` があればフィルタの引数の個数も見る。
|
|
88
|
+
* 区切りの無い式は 2.x のパーサが先に拒否するので何も言わない。
|
|
89
|
+
*/
|
|
90
|
+
declare function findV3MigrationIssues(expr: string, parsed?: ParseBindTextResult | null): string[];
|
|
91
|
+
/** mustache / コメントのテキストバインディング(右辺だけ、`;` で割らない)の判定 */
|
|
92
|
+
declare function findEmbeddedV3MigrationIssues(expression: string, parsed?: ParseBindTextResult | null): string[];
|
|
93
|
+
|
|
74
94
|
/**
|
|
75
95
|
* parser.ts — `data-wcs` バインディング構文の正本パーサを tooling 向けに公開する
|
|
76
96
|
* サブパスエントリ(`@wcstack/state/parser`)。
|
|
@@ -110,5 +130,5 @@ declare function getPathInfo(path: string): IPathInfo;
|
|
|
110
130
|
*/
|
|
111
131
|
declare function clearParserCaches(): void;
|
|
112
132
|
|
|
113
|
-
export { clearParserCaches, getPathInfo, parseBindTextForEmbeddedNode, parseBindTextsForElement };
|
|
133
|
+
export { clearParserCaches, findEmbeddedV3MigrationIssues, findV3MigrationIssues, getPathInfo, parseBindTextForEmbeddedNode, parseBindTextsForElement };
|
|
114
134
|
export type { BindingType, IFilterInfo, IPathInfo, ParseBindTextResult };
|
package/dist/parser.esm.js
CHANGED
|
@@ -323,6 +323,109 @@ function valueMustBeArray(fnName) {
|
|
|
323
323
|
raiseError(`filter ${fnName} requires an array value`);
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
// 組み込みフィルタの引数の上限。正本は filters/filterMeta.ts の maxArgs だが、説明文ごとランタイムに
|
|
327
|
+
// 載せないためここに畳む(一致はテストが固定する)。2.x の組み込みに無い名前はパーサが先に拒否する。
|
|
328
|
+
// 配列リテラルで書く — split() の呼び出しは副作用ありとみなされ、defineState だけの import に残る。
|
|
329
|
+
const NO_ARGS = new Set([
|
|
330
|
+
"not", "abs", "uc", "lc", "cap", "trim", "rev", "int", "float", "date", "time", "datetime",
|
|
331
|
+
"falsy", "truthy", "boolean", "number", "string", "null",
|
|
332
|
+
]);
|
|
333
|
+
const TWO_ARGS = new Set(["clamp", "slice", "substr", "pad", "truncate"]);
|
|
334
|
+
/** 組み込みフィルタ name が受け取る引数の上限 */
|
|
335
|
+
function maxFilterArgs(name) {
|
|
336
|
+
return NO_ARGS.has(name) ? 0 : TWO_ARGS.has(name) ? 2 : 1;
|
|
337
|
+
}
|
|
338
|
+
const STRUCTURAL_KEYWORDS = new Set(["for", "if", "elseif", "else", "..."]);
|
|
339
|
+
/**
|
|
340
|
+
* 引用符の無い true / false / null を 1 つだけ取る eq / ne / defaults。3.0 は型付きの値として読む(要件 B9)。
|
|
341
|
+
* 2.x はフィルタに渡す前に引用符を剥がすので、値の側では `eq('true')` と区別できない — 原文で見る。
|
|
342
|
+
*/
|
|
343
|
+
const TYPED_LITERAL = /(?:^|\|)\s*(eq|ne|defaults)\s*\(\s*(true|false|null)\s*\)/g;
|
|
344
|
+
/** 閉じていない引用符があるか */
|
|
345
|
+
function hasUnterminatedQuote(text) {
|
|
346
|
+
let quote = null;
|
|
347
|
+
for (let i = 0; i < text.length; i++) {
|
|
348
|
+
const c = text[i];
|
|
349
|
+
if (quote !== null) {
|
|
350
|
+
if (c === quote)
|
|
351
|
+
quote = null;
|
|
352
|
+
}
|
|
353
|
+
else if (c === "'" || c === '"') {
|
|
354
|
+
quote = c;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return quote !== null;
|
|
358
|
+
}
|
|
359
|
+
/** 右辺(とフィルタ)の判定。属性の式とテキストバインディングで共通 */
|
|
360
|
+
function checkStatePart(text, parsed, issues) {
|
|
361
|
+
if (hasUnterminatedQuote(text)) {
|
|
362
|
+
issues.push(`"${text}": 3.0 rejects the unterminated quote.`);
|
|
363
|
+
}
|
|
364
|
+
for (const [, fnName, literal] of text.matchAll(TYPED_LITERAL)) {
|
|
365
|
+
issues.push(`"${fnName}(${literal})": 3.0 reads an unquoted ${literal} as a ${literal === "null" ? "null" : "boolean"}, ` +
|
|
366
|
+
`not the text. Write ${fnName}('${literal}') to keep the text.`);
|
|
367
|
+
}
|
|
368
|
+
if (parsed !== null) {
|
|
369
|
+
issues.push(...findFilterArityIssues([parsed]));
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/** 解析済みのフィルタの引数の個数(3.0 は束縛計画の段で [wcs/filter-arity] として拒否する) */
|
|
373
|
+
function findFilterArityIssues(results) {
|
|
374
|
+
const issues = [];
|
|
375
|
+
for (const result of results) {
|
|
376
|
+
for (const filter of [...result.inFilters, ...result.outFilters]) {
|
|
377
|
+
const max = maxFilterArgs(filter.filterName);
|
|
378
|
+
if (filter.args.length > max) {
|
|
379
|
+
issues.push(`"${filter.filterName}" takes at most ${max} argument(s); 3.0 rejects more.`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return issues;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* `data-wcs` の式 1 つ(`;` を含まない、trim 済み)の判定。`parsed` があればフィルタの引数の個数も見る。
|
|
387
|
+
* 区切りの無い式は 2.x のパーサが先に拒否するので何も言わない。
|
|
388
|
+
*/
|
|
389
|
+
function findV3MigrationIssues(expr, parsed = null) {
|
|
390
|
+
const issues = [];
|
|
391
|
+
const colon = expr.indexOf(":");
|
|
392
|
+
if (colon === -1)
|
|
393
|
+
return issues;
|
|
394
|
+
const propPart = expr.slice(0, colon).trim();
|
|
395
|
+
const modifierParts = propPart.split("#");
|
|
396
|
+
const keyword = modifierParts[0].split("|")[0].trim();
|
|
397
|
+
if (modifierParts.length > 2) {
|
|
398
|
+
issues.push(`"${propPart}": 3.0 rejects a second "#". Write "${modifierParts[0].trim()}#${modifierParts.slice(1).map((m) => m.trim()).join(",")}".`);
|
|
399
|
+
}
|
|
400
|
+
if (keyword === "else" && expr.slice(colon + 1).trim().length > 0) {
|
|
401
|
+
issues.push(`"${expr}": 3.0 rejects a value after "else:".`);
|
|
402
|
+
}
|
|
403
|
+
if (STRUCTURAL_KEYWORDS.has(keyword) && keyword !== propPart) {
|
|
404
|
+
issues.push(`"${propPart}": 3.0 rejects modifiers and filters on "${keyword}". Write "${keyword}:".`);
|
|
405
|
+
}
|
|
406
|
+
if ((keyword === "radio" || keyword === "checkbox") && keyword !== propPart) {
|
|
407
|
+
issues.push(`"${propPart}": 3.0 keeps this a ${keyword} binding that honours the modifiers (2.x binds a property named "${keyword}").`);
|
|
408
|
+
}
|
|
409
|
+
checkStatePart(expr.slice(colon + 1).trim(), parsed, issues);
|
|
410
|
+
return issues;
|
|
411
|
+
}
|
|
412
|
+
/** mustache / コメントのテキストバインディング(右辺だけ、`;` で割らない)の判定 */
|
|
413
|
+
function findEmbeddedV3MigrationIssues(expression, parsed = null) {
|
|
414
|
+
const issues = [];
|
|
415
|
+
checkStatePart(expression, parsed, issues);
|
|
416
|
+
return issues;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const warned = new Set();
|
|
420
|
+
/** 同じ文面は 1 回だけ警告する(文面は書き方とサイトを含むので、書き方・サイトごとに 1 回) */
|
|
421
|
+
function warnV3Migration(message) {
|
|
422
|
+
if (warned.has(message)) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
warned.add(message);
|
|
426
|
+
console.warn(`[@wcstack/state] [wcs/v3-migration] ${message} See "Preparing for 3.0" in the @wcstack/state README.`);
|
|
427
|
+
}
|
|
428
|
+
|
|
326
429
|
/**
|
|
327
430
|
* builtinFilters.ts
|
|
328
431
|
*
|
|
@@ -1049,6 +1152,12 @@ const hms = (options) => {
|
|
|
1049
1152
|
return `${hours}${opt}${minutes}${opt}${seconds}`;
|
|
1050
1153
|
};
|
|
1051
1154
|
};
|
|
1155
|
+
/** 3.0 は truthy / falsy / defaults を JavaScript の真偽判定に揃える(要件 B10)— 0n の差を予告する */
|
|
1156
|
+
function warnBigIntZero(fnName, value) {
|
|
1157
|
+
if (value === 0n) {
|
|
1158
|
+
warnV3Migration(`"${fnName}" got 0n: 3.0 treats it as falsy.`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1052
1161
|
/**
|
|
1053
1162
|
* Falsy filter - checks if value is falsy.
|
|
1054
1163
|
*
|
|
@@ -1056,7 +1165,10 @@ const hms = (options) => {
|
|
|
1056
1165
|
* @returns Filter function that returns true for false/null/undefined/0/''/NaN
|
|
1057
1166
|
*/
|
|
1058
1167
|
const falsy = (_options) => {
|
|
1059
|
-
return (value) =>
|
|
1168
|
+
return (value) => {
|
|
1169
|
+
warnBigIntZero('falsy', value);
|
|
1170
|
+
return value === false || value === null || value === undefined || value === 0 || value === '' || Number.isNaN(value);
|
|
1171
|
+
};
|
|
1060
1172
|
};
|
|
1061
1173
|
/**
|
|
1062
1174
|
* Truthy filter - checks if value is truthy.
|
|
@@ -1065,7 +1177,10 @@ const falsy = (_options) => {
|
|
|
1065
1177
|
* @returns Filter function that returns true for non-falsy values
|
|
1066
1178
|
*/
|
|
1067
1179
|
const truthy = (_options) => {
|
|
1068
|
-
return (value) =>
|
|
1180
|
+
return (value) => {
|
|
1181
|
+
warnBigIntZero('truthy', value);
|
|
1182
|
+
return value !== false && value !== null && value !== undefined && value !== 0 && value !== '' && !Number.isNaN(value);
|
|
1183
|
+
};
|
|
1069
1184
|
};
|
|
1070
1185
|
/**
|
|
1071
1186
|
* Default filter - returns default value if input is falsy.
|
|
@@ -1076,6 +1191,7 @@ const truthy = (_options) => {
|
|
|
1076
1191
|
const defaults = (options) => {
|
|
1077
1192
|
const opt = options?.[0] ?? optionsRequired('defaults');
|
|
1078
1193
|
return (value) => {
|
|
1194
|
+
warnBigIntZero('defaults', value);
|
|
1079
1195
|
if (value === false || value === null || value === undefined || value === 0 || value === '' || Number.isNaN(value)) {
|
|
1080
1196
|
return opt;
|
|
1081
1197
|
}
|
|
@@ -1567,4 +1683,4 @@ function clearParserCaches() {
|
|
|
1567
1683
|
clearFilterFnCacheForTooling();
|
|
1568
1684
|
}
|
|
1569
1685
|
|
|
1570
|
-
export { clearParserCaches, getPathInfo, parseBindTextForEmbeddedNode, parseBindTextsForElement };
|
|
1686
|
+
export { clearParserCaches, findEmbeddedV3MigrationIssues, findV3MigrationIssues, getPathInfo, parseBindTextForEmbeddedNode, parseBindTextsForElement };
|
package/package.json
CHANGED