@wcstack/lint 1.25.0 → 1.27.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/cli.cjs +310 -75
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -89,6 +89,13 @@ var WcsDiagnosticCode = {
|
|
|
89
89
|
TokenUndeclared: "wcs/token-undeclared",
|
|
90
90
|
TokenMisconfigured: "wcs/token-misconfigured",
|
|
91
91
|
NestedAssign: "wcs/nested-assign",
|
|
92
|
+
// --- <wcs-state> script: $watch declaration ---
|
|
93
|
+
// ランタイム(watch/processWatchDeclaration.ts)が raiseError で落とす宣言。
|
|
94
|
+
// 越境 `@` / `$` 始まり / 空キー・空セグメント / 明らかな非関数ハンドラ。
|
|
95
|
+
WatchDeclarationInvalid: "wcs/watch-declaration-invalid",
|
|
96
|
+
// `$watch` のキーが状態定義に存在しない。バインディング側と違い黙って発火しない
|
|
97
|
+
// だけなので気づけない。severity は binding-path-missing に揃える(warning)。
|
|
98
|
+
WatchPathMissing: "wcs/watch-path-missing",
|
|
92
99
|
TypeAnnotation: "wcs/type-annotation",
|
|
93
100
|
TemplateSyntax: "wcs/template-syntax",
|
|
94
101
|
// --- <wcs-state> script: array reactivity hazards ---
|
|
@@ -134,6 +141,8 @@ var builtinFilterMeta = {
|
|
|
134
141
|
mul: { description: "\u4E57\u7B97", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
135
142
|
div: { description: "\u9664\u7B97", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
136
143
|
mod: { description: "\u5270\u4F59", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
144
|
+
abs: { description: "\u7D76\u5BFE\u5024", hasArgs: false, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 0 },
|
|
145
|
+
clamp: { description: "\u7BC4\u56F2\u5185\u306B\u4E38\u3081\u308B (min,max)", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 2, maxArgs: 2, argTypes: ["number", "number"] },
|
|
137
146
|
// 数値フォーマット
|
|
138
147
|
fix: { description: "\u56FA\u5B9A\u5C0F\u6570\u70B9\u8868\u8A18", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
|
|
139
148
|
locale: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u3067\u6570\u5024\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
@@ -147,6 +156,8 @@ var builtinFilterMeta = {
|
|
|
147
156
|
pad: { description: "\u30D1\u30C7\u30A3\u30F3\u30B0 (length[,char])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
|
|
148
157
|
rep: { description: "\u7E70\u308A\u8FD4\u3057 (count)", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 1, argTypes: ["number"] },
|
|
149
158
|
rev: { description: "\u6587\u5B57\u9806\u3092\u53CD\u8EE2", hasArgs: false, resultType: "string", acceptTypes: ["string"], minArgs: 0, maxArgs: 0 },
|
|
159
|
+
truncate: { description: "\u5207\u308A\u8A70\u3081\u3066\u7701\u7565\u8A18\u53F7 (length[,suffix])", hasArgs: true, resultType: "string", acceptTypes: ["string"], minArgs: 1, maxArgs: 2, argTypes: ["number", "string"] },
|
|
160
|
+
join: { description: "\u914D\u5217\u3092\u9023\u7D50 ([separator])", hasArgs: true, resultType: "string", acceptTypes: ["array"], minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
150
161
|
// 数値パース・丸め
|
|
151
162
|
int: { description: "\u6574\u6570\u306B\u30D1\u30FC\u30B9", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
|
|
152
163
|
float: { description: "\u6D6E\u52D5\u5C0F\u6570\u70B9\u6570\u306B\u30D1\u30FC\u30B9", hasArgs: false, resultType: "number", acceptTypes: ["string", "number"], minArgs: 0, maxArgs: 0 },
|
|
@@ -154,11 +165,15 @@ var builtinFilterMeta = {
|
|
|
154
165
|
floor: { description: "\u5207\u308A\u4E0B\u3052", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
|
|
155
166
|
ceil: { description: "\u5207\u308A\u4E0A\u3052", hasArgs: true, resultType: "number", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
|
|
156
167
|
percent: { description: "\u30D1\u30FC\u30BB\u30F3\u30C6\u30FC\u30B8\u5F62\u5F0F", hasArgs: true, resultType: "string", acceptTypes: ["number"], minArgs: 0, maxArgs: 1, argTypes: ["number"] },
|
|
168
|
+
// number だけでなく string も受ける。実用チェーンは fix / percent の後ろに繋がり、
|
|
169
|
+
// それらは既に string を返すため(builtinFilters.ts の unit を参照)
|
|
170
|
+
unit: { description: "\u5358\u4F4D\uFF08\u63A5\u5C3E\u8F9E\uFF09\u3092\u4ED8\u52A0", hasArgs: true, resultType: "string", acceptTypes: ["number", "string"], minArgs: 1, maxArgs: 1, argTypes: ["string"] },
|
|
157
171
|
// 日付・時刻
|
|
158
172
|
date: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u65E5\u4ED8", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
159
173
|
time: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u6642\u523B", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
160
174
|
datetime: { description: "\u30ED\u30B1\u30FC\u30EB\u5F62\u5F0F\u306E\u65E5\u6642", hasArgs: false, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
161
175
|
ymd: { description: "YYYY-MM-DD \u5F62\u5F0F", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
176
|
+
hms: { description: "HH:MM:SS \u5F62\u5F0F", hasArgs: true, resultType: "string", acceptTypes: "any", minArgs: 0, maxArgs: 1, argTypes: ["string"] },
|
|
162
177
|
// 真偽値・変換
|
|
163
178
|
falsy: { description: "\u507D\u5024\u304B\u5224\u5B9A", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
164
179
|
truthy: { description: "\u771F\u5024\u304B\u5224\u5B9A", hasArgs: false, resultType: "boolean", acceptTypes: "any", minArgs: 0, maxArgs: 0 },
|
|
@@ -418,15 +433,18 @@ function extractAttribute(tagContent, attrName) {
|
|
|
418
433
|
var RESERVED_STREAMS_KEY = "$streams";
|
|
419
434
|
var RESERVED_COMMAND_TOKENS_KEY = "$commandTokens";
|
|
420
435
|
var RESERVED_EVENT_TOKENS_KEY = "$eventTokens";
|
|
436
|
+
var RESERVED_LIST_KEYS_KEY = "$listKeys";
|
|
437
|
+
var RESERVED_WATCH_KEY = "$watch";
|
|
421
438
|
function analyzeStatePaths(scriptContent, stateName = "default") {
|
|
422
439
|
const objectContent = extractDefaultExportObject(scriptContent);
|
|
423
440
|
if (!objectContent) return [];
|
|
424
441
|
const paths = [];
|
|
425
442
|
const topLevelProps = parseTopLevelProperties(objectContent);
|
|
426
443
|
const pendingStreamValues = [];
|
|
444
|
+
const pendingListKeys = [];
|
|
427
445
|
for (const prop of topLevelProps) {
|
|
428
446
|
if (prop.name.startsWith("$")) {
|
|
429
|
-
collectReservedKeyPaths(prop, paths, pendingStreamValues, stateName);
|
|
447
|
+
collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys, stateName);
|
|
430
448
|
continue;
|
|
431
449
|
}
|
|
432
450
|
if (prop.kind === "method") {
|
|
@@ -434,7 +452,9 @@ function analyzeStatePaths(scriptContent, stateName = "default") {
|
|
|
434
452
|
continue;
|
|
435
453
|
}
|
|
436
454
|
if (prop.kind === "getter") {
|
|
437
|
-
paths.
|
|
455
|
+
if (!paths.some((p) => p.stateName === stateName && p.path === prop.name)) {
|
|
456
|
+
paths.push({ path: prop.name, kind: "computed", stateName });
|
|
457
|
+
}
|
|
438
458
|
continue;
|
|
439
459
|
}
|
|
440
460
|
pushDataPropertyPaths(prop, paths, stateName);
|
|
@@ -443,9 +463,42 @@ function analyzeStatePaths(scriptContent, stateName = "default") {
|
|
|
443
463
|
if (paths.some((p) => p.stateName === stateName && p.path === streamValue.name)) continue;
|
|
444
464
|
pushDataPropertyPaths(streamValue, paths, stateName);
|
|
445
465
|
}
|
|
466
|
+
for (const listKeyEntry of pendingListKeys) {
|
|
467
|
+
pushListKeyPaths(listKeyEntry, paths, stateName);
|
|
468
|
+
}
|
|
446
469
|
return paths;
|
|
447
470
|
}
|
|
448
|
-
function
|
|
471
|
+
function analyzeWatchEntries(scriptContent) {
|
|
472
|
+
const root = locateDefaultExportObject(scriptContent);
|
|
473
|
+
if (!root) return [];
|
|
474
|
+
const watchProp = parseTopLevelProperties(root.content).find((p) => p.name === RESERVED_WATCH_KEY);
|
|
475
|
+
if (!watchProp || watchProp.kind !== "data" || !watchProp.value || !isObjectLiteral(watchProp.value) || watchProp.valueStart === void 0) {
|
|
476
|
+
return [];
|
|
477
|
+
}
|
|
478
|
+
const leading = watchProp.value.length - watchProp.value.trimStart().length;
|
|
479
|
+
const innerStart = root.start + watchProp.valueStart + leading + 1;
|
|
480
|
+
const entries = [];
|
|
481
|
+
for (const entry of parseTopLevelProperties(extractObjectContent(watchProp.value))) {
|
|
482
|
+
if (entry.nameStart === void 0 || entry.nameEnd === void 0) continue;
|
|
483
|
+
entries.push({
|
|
484
|
+
key: entry.name,
|
|
485
|
+
start: innerStart + entry.nameStart,
|
|
486
|
+
end: innerStart + entry.nameEnd,
|
|
487
|
+
// メソッド短縮記法は関数。data は値リテラルの形で判定し、識別子参照は疑わない。
|
|
488
|
+
definitelyNotFunction: entry.kind === "data" && isNonFunctionLiteral(entry.value)
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
return entries;
|
|
492
|
+
}
|
|
493
|
+
function isNonFunctionLiteral(value) {
|
|
494
|
+
if (value === void 0) return false;
|
|
495
|
+
const trimmed = value.trim();
|
|
496
|
+
if (trimmed.length === 0) return false;
|
|
497
|
+
const scan = maskCommentsAndStrings(trimmed);
|
|
498
|
+
if (/^(?:async\s+)?function\b/.test(trimmed) || scan.includes("=>")) return false;
|
|
499
|
+
return /^["'`]/.test(trimmed) || /^-?\d/.test(trimmed) || /^(?:true|false|null|undefined)\b/.test(trimmed) || trimmed.startsWith("[") || trimmed.startsWith("{");
|
|
500
|
+
}
|
|
501
|
+
function collectReservedKeyPaths(prop, paths, pendingStreamValues, pendingListKeys, stateName) {
|
|
449
502
|
if (prop.name === RESERVED_STREAMS_KEY && prop.kind === "data" && prop.value && isObjectLiteral(prop.value)) {
|
|
450
503
|
const entries = parseTopLevelProperties(extractObjectContent(prop.value));
|
|
451
504
|
for (const entry of entries) {
|
|
@@ -474,6 +527,36 @@ function collectReservedKeyPaths(prop, paths, pendingStreamValues, stateName) {
|
|
|
474
527
|
}
|
|
475
528
|
return;
|
|
476
529
|
}
|
|
530
|
+
if (prop.name === RESERVED_LIST_KEYS_KEY && prop.kind === "data" && prop.value && isObjectLiteral(prop.value)) {
|
|
531
|
+
for (const entry of parseTopLevelProperties(extractObjectContent(prop.value))) {
|
|
532
|
+
if (entry.kind !== "data") continue;
|
|
533
|
+
pendingListKeys.push(entry);
|
|
534
|
+
}
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function pushListKeyPaths(entry, paths, stateName) {
|
|
539
|
+
const listPath = entry.name;
|
|
540
|
+
const segments = listPath.split(".");
|
|
541
|
+
if (listPath.length === 0 || segments.some((s) => s.length === 0) || segments[segments.length - 1] === "*") {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const has = (path) => paths.some((p) => p.stateName === stateName && p.path === path);
|
|
545
|
+
if (!has(listPath)) paths.push({ path: listPath, kind: "data", typeHint: "array", stateName });
|
|
546
|
+
if (!has(`${listPath}.*`)) paths.push({ path: `${listPath}.*`, kind: "list", stateName });
|
|
547
|
+
if (!has(`${listPath}.length`)) {
|
|
548
|
+
paths.push({ path: `${listPath}.length`, kind: "data", typeHint: "number", stateName });
|
|
549
|
+
}
|
|
550
|
+
const keyField = extractStringLiteralValue(entry.value);
|
|
551
|
+
if (keyField === null || keyField.includes(".") || keyField.includes("*")) return;
|
|
552
|
+
if (!has(`${listPath}.*.${keyField}`)) {
|
|
553
|
+
paths.push({ path: `${listPath}.*.${keyField}`, kind: "data", stateName });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function extractStringLiteralValue(value) {
|
|
557
|
+
if (!value) return null;
|
|
558
|
+
const match = value.trim().match(/^["']([^"'\\]*)["']$/);
|
|
559
|
+
return match && match[1].length > 0 ? match[1] : null;
|
|
477
560
|
}
|
|
478
561
|
function findStreamInitialProperty(entryValue) {
|
|
479
562
|
const defProps = parseTopLevelProperties(extractObjectContent(entryValue));
|
|
@@ -489,46 +572,32 @@ function extractStringArrayItems(value) {
|
|
|
489
572
|
}
|
|
490
573
|
return items;
|
|
491
574
|
}
|
|
575
|
+
var MAX_OBJECT_NEST_DEPTH = 5;
|
|
492
576
|
function pushDataPropertyPaths(prop, paths, stateName) {
|
|
493
|
-
|
|
577
|
+
pushDataPropertyPathsAt(prop.name, prop, paths, stateName, 0);
|
|
578
|
+
}
|
|
579
|
+
function pushDataPropertyPathsAt(path, prop, paths, stateName, depth) {
|
|
580
|
+
paths.push({ path, kind: "data", typeHint: prop.typeHint, rawInitial: prop.value?.trim(), stateName });
|
|
494
581
|
if (prop.value && isArrayLiteral(prop.value)) {
|
|
495
|
-
paths.push({ path: `${
|
|
496
|
-
paths.push({ path: `${
|
|
582
|
+
paths.push({ path: `${path}.*`, kind: "list", stateName });
|
|
583
|
+
paths.push({ path: `${path}.length`, kind: "data", typeHint: "number", stateName });
|
|
497
584
|
const elementProps = extractArrayElementProperties(prop.value);
|
|
498
585
|
for (const childProp of elementProps) {
|
|
499
586
|
paths.push({
|
|
500
|
-
path: `${
|
|
587
|
+
path: `${path}.*.${childProp.name}`,
|
|
501
588
|
kind: "data",
|
|
502
589
|
typeHint: childProp.typeHint,
|
|
503
590
|
stateName
|
|
504
591
|
});
|
|
505
592
|
}
|
|
593
|
+
return;
|
|
506
594
|
}
|
|
507
595
|
if (prop.value && isObjectLiteral(prop.value)) {
|
|
596
|
+
if (depth >= MAX_OBJECT_NEST_DEPTH) return;
|
|
508
597
|
const childProps = parseTopLevelProperties(extractObjectContent(prop.value));
|
|
509
598
|
for (const childProp of childProps) {
|
|
510
|
-
if (childProp.kind
|
|
511
|
-
|
|
512
|
-
path: `${prop.name}.${childProp.name}`,
|
|
513
|
-
kind: "data",
|
|
514
|
-
typeHint: childProp.typeHint,
|
|
515
|
-
rawInitial: childProp.value?.trim(),
|
|
516
|
-
stateName
|
|
517
|
-
});
|
|
518
|
-
if (childProp.value && isArrayLiteral(childProp.value)) {
|
|
519
|
-
paths.push({ path: `${prop.name}.${childProp.name}.*`, kind: "list", stateName });
|
|
520
|
-
paths.push({ path: `${prop.name}.${childProp.name}.length`, kind: "data", typeHint: "number", stateName });
|
|
521
|
-
const grandchildProps = extractArrayElementProperties(childProp.value);
|
|
522
|
-
for (const gc of grandchildProps) {
|
|
523
|
-
paths.push({
|
|
524
|
-
path: `${prop.name}.${childProp.name}.*.${gc.name}`,
|
|
525
|
-
kind: "data",
|
|
526
|
-
typeHint: gc.typeHint,
|
|
527
|
-
stateName
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
}
|
|
599
|
+
if (childProp.kind !== "data") continue;
|
|
600
|
+
pushDataPropertyPathsAt(`${path}.${childProp.name}`, childProp, paths, stateName, depth + 1);
|
|
532
601
|
}
|
|
533
602
|
}
|
|
534
603
|
}
|
|
@@ -575,53 +644,109 @@ function inferJsonTypeHint(value) {
|
|
|
575
644
|
if (typeof value === "object") return "object";
|
|
576
645
|
return void 0;
|
|
577
646
|
}
|
|
578
|
-
function
|
|
579
|
-
const
|
|
647
|
+
function locateDefaultExportObject(script) {
|
|
648
|
+
const scan = maskCommentsAndStrings(script);
|
|
649
|
+
const match = scan.match(/export\s+default\s+(?:defineState\s*\(\s*)?(\{)/);
|
|
580
650
|
if (!match) return null;
|
|
581
|
-
const
|
|
582
|
-
return extractBracedContent(script,
|
|
651
|
+
const braceIndex = scan.indexOf(match[1], match.index);
|
|
652
|
+
return { content: extractBracedContent(script, scan, braceIndex), start: braceIndex + 1 };
|
|
653
|
+
}
|
|
654
|
+
function extractDefaultExportObject(script) {
|
|
655
|
+
return locateDefaultExportObject(script)?.content ?? null;
|
|
583
656
|
}
|
|
584
657
|
function parseTopLevelProperties(objectContent) {
|
|
585
658
|
const props = [];
|
|
586
|
-
const
|
|
659
|
+
const scan = maskCommentsAndStrings(objectContent);
|
|
660
|
+
const regex = /(?:(?:get|set)\s+(?:"([^"]+)"|'([^']+)'|([$\w]+))\s*\([^)]*\)\s*\{)|(?:(?:async\s+)?(?:"([^"]+)"|'([^']+)'|([$\w]+))\s*\([^)]*\)\s*\{)|(?:(?:"([^"]+)"|'([^']+)'|([$\w]+))\s*:\s*)/gd;
|
|
587
661
|
let match;
|
|
588
|
-
while ((match = regex.exec(
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
|
|
662
|
+
while ((match = regex.exec(scan)) !== null) {
|
|
663
|
+
const indices = match.indices;
|
|
664
|
+
let nameSpan;
|
|
665
|
+
const nameAt = (group) => {
|
|
666
|
+
const span = indices[group];
|
|
667
|
+
if (!span) return void 0;
|
|
668
|
+
nameSpan = [span[0], span[1]];
|
|
669
|
+
return objectContent.slice(span[0], span[1]);
|
|
670
|
+
};
|
|
671
|
+
const skipBody = () => {
|
|
672
|
+
const braceStart = match.index + match[0].length - 1;
|
|
673
|
+
const body = extractBracedContent(objectContent, scan, braceStart);
|
|
674
|
+
regex.lastIndex = braceStart + body.length + 2;
|
|
675
|
+
};
|
|
676
|
+
const accessorName = nameAt(1) ?? nameAt(2) ?? nameAt(3);
|
|
677
|
+
if (accessorName) {
|
|
678
|
+
props.push({ name: accessorName, kind: "getter", nameStart: nameSpan[0], nameEnd: nameSpan[1] });
|
|
679
|
+
skipBody();
|
|
592
680
|
continue;
|
|
593
681
|
}
|
|
594
|
-
const methodName =
|
|
682
|
+
const methodName = nameAt(4) ?? nameAt(5) ?? nameAt(6);
|
|
595
683
|
if (methodName) {
|
|
596
|
-
props.push({ name: methodName, kind: "method" });
|
|
597
|
-
|
|
598
|
-
if (braceStart !== -1) {
|
|
599
|
-
const body = extractBracedContent(objectContent, braceStart);
|
|
600
|
-
regex.lastIndex = braceStart + body.length + 2;
|
|
601
|
-
}
|
|
684
|
+
props.push({ name: methodName, kind: "method", nameStart: nameSpan[0], nameEnd: nameSpan[1] });
|
|
685
|
+
skipBody();
|
|
602
686
|
continue;
|
|
603
687
|
}
|
|
604
|
-
const propName =
|
|
688
|
+
const propName = nameAt(7) ?? nameAt(8) ?? nameAt(9);
|
|
605
689
|
if (propName) {
|
|
606
690
|
const valueStartIndex = match.index + match[0].length;
|
|
607
|
-
const value = extractFullValue(objectContent, valueStartIndex);
|
|
691
|
+
const value = extractFullValue(objectContent, scan, valueStartIndex);
|
|
608
692
|
const jsdocType = extractJsDocType(objectContent, match.index);
|
|
609
693
|
const typeHint = jsdocType ?? inferTypeHint(value);
|
|
610
|
-
props.push({
|
|
694
|
+
props.push({
|
|
695
|
+
name: propName,
|
|
696
|
+
kind: "data",
|
|
697
|
+
value,
|
|
698
|
+
typeHint,
|
|
699
|
+
nameStart: nameSpan[0],
|
|
700
|
+
nameEnd: nameSpan[1],
|
|
701
|
+
valueStart: valueStartIndex
|
|
702
|
+
});
|
|
611
703
|
regex.lastIndex = valueStartIndex + value.length;
|
|
612
704
|
}
|
|
613
705
|
}
|
|
614
706
|
return props;
|
|
615
707
|
}
|
|
616
|
-
function
|
|
708
|
+
function maskCommentsAndStrings(source) {
|
|
709
|
+
const out = source.split("");
|
|
710
|
+
const len = source.length;
|
|
711
|
+
const blank = (i2) => {
|
|
712
|
+
if (source[i2] !== "\n" && source[i2] !== "\r") out[i2] = " ";
|
|
713
|
+
};
|
|
714
|
+
let i = 0;
|
|
715
|
+
while (i < len) {
|
|
716
|
+
const ch = source[i];
|
|
717
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
718
|
+
i += 2;
|
|
719
|
+
while (i < len && source[i] !== "\n") blank(i++);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
723
|
+
i += 2;
|
|
724
|
+
while (i < len && !(source[i] === "*" && source[i + 1] === "/")) blank(i++);
|
|
725
|
+
i += 2;
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
729
|
+
i++;
|
|
730
|
+
while (i < len && source[i] !== ch) {
|
|
731
|
+
if (source[i] === "\\") blank(i++);
|
|
732
|
+
if (i < len) blank(i++);
|
|
733
|
+
}
|
|
734
|
+
i++;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
i++;
|
|
738
|
+
}
|
|
739
|
+
return out.join("");
|
|
740
|
+
}
|
|
741
|
+
function extractFullValue(content, scan, startIndex) {
|
|
617
742
|
let depth = 0;
|
|
618
743
|
let i = startIndex;
|
|
619
|
-
const len =
|
|
744
|
+
const len = scan.length;
|
|
620
745
|
let inString = null;
|
|
621
746
|
while (i < len) {
|
|
622
|
-
const ch =
|
|
747
|
+
const ch = scan[i];
|
|
623
748
|
if (inString) {
|
|
624
|
-
if (ch === inString && !isEscaped(
|
|
749
|
+
if (ch === inString && !isEscaped(scan, i)) {
|
|
625
750
|
inString = null;
|
|
626
751
|
}
|
|
627
752
|
i++;
|
|
@@ -641,13 +766,13 @@ function extractFullValue(content, startIndex) {
|
|
|
641
766
|
}
|
|
642
767
|
return content.slice(startIndex, i).trim();
|
|
643
768
|
}
|
|
644
|
-
function extractBracedContent(text, openBraceIndex) {
|
|
769
|
+
function extractBracedContent(text, scan, openBraceIndex) {
|
|
645
770
|
let depth = 0;
|
|
646
771
|
let inString = null;
|
|
647
|
-
for (let i = openBraceIndex; i <
|
|
648
|
-
const ch =
|
|
772
|
+
for (let i = openBraceIndex; i < scan.length; i++) {
|
|
773
|
+
const ch = scan[i];
|
|
649
774
|
if (inString) {
|
|
650
|
-
if (ch === inString && !isEscaped(
|
|
775
|
+
if (ch === inString && !isEscaped(scan, i)) {
|
|
651
776
|
inString = null;
|
|
652
777
|
}
|
|
653
778
|
continue;
|
|
@@ -673,16 +798,18 @@ function isObjectLiteral(value) {
|
|
|
673
798
|
}
|
|
674
799
|
function extractObjectContent(value) {
|
|
675
800
|
const trimmed = value.trim();
|
|
676
|
-
const
|
|
801
|
+
const scan = maskCommentsAndStrings(trimmed);
|
|
802
|
+
const start = scan.indexOf("{");
|
|
677
803
|
if (start === -1) return "";
|
|
678
|
-
return extractBracedContent(trimmed, start);
|
|
804
|
+
return extractBracedContent(trimmed, scan, start);
|
|
679
805
|
}
|
|
680
806
|
function extractArrayElementProperties(value) {
|
|
681
807
|
const trimmed = value.trim();
|
|
682
808
|
if (!trimmed.startsWith("[")) return [];
|
|
683
|
-
const
|
|
809
|
+
const scan = maskCommentsAndStrings(trimmed);
|
|
810
|
+
const objectStart = scan.indexOf("{");
|
|
684
811
|
if (objectStart === -1) return [];
|
|
685
|
-
const objectContent = extractBracedContent(trimmed, objectStart);
|
|
812
|
+
const objectContent = extractBracedContent(trimmed, scan, objectStart);
|
|
686
813
|
const props = [];
|
|
687
814
|
const allProps = parseTopLevelProperties(objectContent);
|
|
688
815
|
for (const prop of allProps) {
|
|
@@ -904,6 +1031,11 @@ var ja = {
|
|
|
904
1031
|
wcsTextInfo: (e) => `wcs-text \u30D0\u30A4\u30F3\u30C7\u30A3\u30F3\u30B0: ${e}`,
|
|
905
1032
|
moustacheFouc: (e) => `<template> \u5916\u306E {{ }} \u69CB\u6587\u306F FOUC\uFF08\u521D\u671F\u8868\u793A\u6642\u306B\u30C6\u30F3\u30D7\u30EC\u30FC\u30C8\u6587\u5B57\u5217\u304C\u898B\u3048\u308B\uFF09\u306E\u539F\u56E0\u306B\u306A\u308A\u307E\u3059\u3002<!--@@:${e}--> \u307E\u305F\u306F\u30B3\u30E1\u30F3\u30C8\u69CB\u6587\u306E\u4F7F\u7528\u3092\u691C\u8A0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
|
|
906
1033
|
nestedAssign: (sp) => `\u30CD\u30B9\u30C8\u3055\u308C\u305F\u30D7\u30ED\u30D1\u30C6\u30A3\u3078\u306E\u4EE3\u5165\u306F\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u66F4\u65B0\u3092\u30C8\u30EA\u30AC\u30FC\u3057\u307E\u305B\u3093\u3002this["${sp}"] \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
|
|
1034
|
+
watchKeyCrossState: (k) => `$watch \u306E\u30AD\u30FC "${k}" \u306F\u4ED6\u306E state \u3092\u6307\u3057\u3066\u3044\u307E\u3059\u3002@ \u4ED8\u304D\u306E\u8D8A\u5883 watch \u306F\u4F7F\u3048\u307E\u305B\u3093\uFF08\u81EA state \u306E\u30D1\u30B9\u306E\u307F\uFF09`,
|
|
1035
|
+
watchKeyReserved: (k) => `$watch \u306E\u30AD\u30FC "${k}" \u306F "$" \u3067\u59CB\u3081\u3089\u308C\u307E\u305B\u3093\uFF08\u4E88\u7D04\u540D\u524D\u7A7A\u9593\uFF09`,
|
|
1036
|
+
watchKeyEmptySegment: (k) => `$watch \u306E\u30AD\u30FC "${k}" \u306B\u7A7A\u306E\u30D1\u30B9\u30BB\u30B0\u30E1\u30F3\u30C8\u304C\u3042\u308A\u307E\u3059`,
|
|
1037
|
+
watchHandlerNotFunction: (k) => `$watch \u306E\u30A8\u30F3\u30C8\u30EA "${k}" \u306E\u5024\u306F\u95A2\u6570\u3067\u3042\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059`,
|
|
1038
|
+
watchPathMissing: (k) => `$watch \u306E\u30AD\u30FC "${k}" \u306F\u72B6\u614B\u5B9A\u7FA9\u306B\u5B58\u5728\u3057\u307E\u305B\u3093\uFF08\u4E00\u5EA6\u3082\u767A\u706B\u3057\u307E\u305B\u3093\uFF09`,
|
|
907
1039
|
typeAnnotationIncompatible: (vt, rt) => `\u578B "${vt}" \u306F @type {${rt}} \u3068\u4E92\u63DB\u6027\u304C\u3042\u308A\u307E\u305B\u3093`,
|
|
908
1040
|
arrayMutation: (m, alt) => `\u914D\u5217\u306E\u7834\u58CA\u7684\u30E1\u30BD\u30C3\u30C9 "${m}" \u306F\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u66F4\u65B0\u3092\u30C8\u30EA\u30AC\u30FC\u3057\u307E\u305B\u3093\uFF08\u540C\u4E00\u53C2\u7167\u306E\u81EA\u5DF1\u518D\u4EE3\u5165\u3067\u3082\u8981\u7D20\u306E\u8FFD\u52A0\u30FB\u524A\u9664\u306F\u53CD\u6620\u3055\u308C\u307E\u305B\u3093\uFF09\u3002\u975E\u7834\u58CA\u30E1\u30BD\u30C3\u30C9\u3068\u518D\u4EE3\u5165\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: ${alt}\uFF09\u3002`,
|
|
909
1041
|
arrayIndexAssign: (sp) => `\u914D\u5217\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3078\u306E\u76F4\u63A5\u4EE3\u5165\u306F\u30EA\u30A2\u30AF\u30C6\u30A3\u30D6\u66F4\u65B0\u3092\u30C8\u30EA\u30AC\u30FC\u3057\u307E\u305B\u3093\u3002this["${sp}"] \u306E\u3088\u3046\u306A\u30C9\u30C3\u30C8\u30D1\u30B9\u4EE3\u5165\u3001\u307E\u305F\u306F with() \u3068\u518D\u4EE3\u5165\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002`,
|
|
@@ -946,6 +1078,11 @@ var en = {
|
|
|
946
1078
|
wcsTextInfo: (e) => `wcs-text binding: ${e}`,
|
|
947
1079
|
moustacheFouc: (e) => `{{ }} outside a <template> causes FOUC (the raw template string is visible before binding). Consider the comment syntax <!--@@:${e}--> instead.`,
|
|
948
1080
|
nestedAssign: (sp) => `Assigning to a nested property does not trigger a reactive update. Use this["${sp}"] instead.`,
|
|
1081
|
+
watchKeyCrossState: (k) => `$watch key "${k}" targets another state. Cross-state watching with @ is not supported (own paths only)`,
|
|
1082
|
+
watchKeyReserved: (k) => `$watch key "${k}" must not start with "$" (reserved namespace)`,
|
|
1083
|
+
watchKeyEmptySegment: (k) => `$watch key "${k}" has an empty path segment`,
|
|
1084
|
+
watchHandlerNotFunction: (k) => `The value of $watch entry "${k}" must be a function`,
|
|
1085
|
+
watchPathMissing: (k) => `$watch key "${k}" does not exist in the state definition (it will never fire)`,
|
|
949
1086
|
typeAnnotationIncompatible: (vt, rt) => `Type "${vt}" is not compatible with @type {${rt}}`,
|
|
950
1087
|
arrayMutation: (m, alt) => `Destructive array method "${m}" does not trigger a reactive update (re-assigning the same reference does not reflect added/removed elements either). Use a non-destructive method with reassignment (e.g. ${alt}).`,
|
|
951
1088
|
arrayIndexAssign: (sp) => `Assigning directly to an array index does not trigger a reactive update. Use a dot-path assignment like this["${sp}"], or with() plus reassignment.`,
|
|
@@ -978,6 +1115,11 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale) {
|
|
|
978
1115
|
pathsByState.set(p.stateName, list);
|
|
979
1116
|
}
|
|
980
1117
|
const attrs = findAllBindAttributes(html, attrName);
|
|
1118
|
+
let structuralTemplates = null;
|
|
1119
|
+
const getStructuralTemplates = () => {
|
|
1120
|
+
structuralTemplates ??= collectStructuralTemplates(html, attrName);
|
|
1121
|
+
return structuralTemplates;
|
|
1122
|
+
};
|
|
981
1123
|
const filterNameSet = new Set(BUILTIN_FILTERS.map((f) => f.name));
|
|
982
1124
|
for (const attr of attrs) {
|
|
983
1125
|
const bindings = splitBindingExpressions(attr.value);
|
|
@@ -1063,7 +1205,7 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale) {
|
|
|
1063
1205
|
if (pathTrimmed.startsWith(".")) {
|
|
1064
1206
|
const forPath = getInnermostForPath(html, attr.valueStart, attrName);
|
|
1065
1207
|
if (forPath && !forPath.startsWith(".")) {
|
|
1066
|
-
checkPath = `${forPath}.*.${pathTrimmed.slice(1)}`;
|
|
1208
|
+
checkPath = pathTrimmed === "." ? `${forPath}.*` : `${forPath}.*.${pathTrimmed.slice(1)}`;
|
|
1067
1209
|
} else {
|
|
1068
1210
|
checkPath = "";
|
|
1069
1211
|
}
|
|
@@ -1172,7 +1314,10 @@ function validateBindings(html, attrName, stateTagName = "wcs-state", locale) {
|
|
|
1172
1314
|
if (pathTrimmed && !pathTrimmed.startsWith(".") && !isLiteral(pathTrimmed)) {
|
|
1173
1315
|
const resultType = resolveResultType(pathTrimmed, parsed.filters, scopedPaths);
|
|
1174
1316
|
if (resultType !== null) {
|
|
1175
|
-
const typeReq = getExpectedType(
|
|
1317
|
+
const typeReq = getExpectedType(
|
|
1318
|
+
parsed.property,
|
|
1319
|
+
() => isNegatedByElseChain(getStructuralTemplates(), attr.valueStart)
|
|
1320
|
+
);
|
|
1176
1321
|
if (typeReq && resultType !== typeReq.expected) {
|
|
1177
1322
|
const pathOffset = binding.indexOf(parsed.path);
|
|
1178
1323
|
const pathStart = bindingStart + pathOffset;
|
|
@@ -1352,12 +1497,54 @@ function validatePathExistence(checkPath, displayPath, scopedPaths, scopedPathSe
|
|
|
1352
1497
|
}
|
|
1353
1498
|
return null;
|
|
1354
1499
|
}
|
|
1355
|
-
function
|
|
1500
|
+
function collectStructuralTemplates(html, attrName) {
|
|
1501
|
+
const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1502
|
+
const attrRegex = new RegExp(`${escaped}\\s*=\\s*(["'])`, "i");
|
|
1503
|
+
const tagRegex = /<template(?:\s[^>]*)?>|<\/template\s*>/gi;
|
|
1504
|
+
const templates = [];
|
|
1505
|
+
let depth = 0;
|
|
1506
|
+
let match;
|
|
1507
|
+
while ((match = tagRegex.exec(html)) !== null) {
|
|
1508
|
+
if (match[0].startsWith("</")) {
|
|
1509
|
+
depth = Math.max(0, depth - 1);
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
const attrMatch = attrRegex.exec(match[0]);
|
|
1513
|
+
if (attrMatch) {
|
|
1514
|
+
const quote = attrMatch[1];
|
|
1515
|
+
const valueStart = match.index + attrMatch.index + attrMatch[0].length;
|
|
1516
|
+
const valueEnd = html.indexOf(quote, valueStart);
|
|
1517
|
+
if (valueEnd !== -1) {
|
|
1518
|
+
const first = splitBindingExpressions(html.slice(valueStart, valueEnd))[0] ?? "";
|
|
1519
|
+
const prop = first.split(":")[0].replace(/#.*$/, "").trim();
|
|
1520
|
+
const type = prop === "if" || prop === "elseif" || prop === "else" ? prop : "other";
|
|
1521
|
+
templates.push({ valueStart, depth, type });
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
depth++;
|
|
1525
|
+
}
|
|
1526
|
+
return templates;
|
|
1527
|
+
}
|
|
1528
|
+
function isNegatedByElseChain(templates, valueStart) {
|
|
1529
|
+
const index = templates.findIndex((t) => t.valueStart === valueStart);
|
|
1530
|
+
if (index === -1) return false;
|
|
1531
|
+
const selfDepth = templates[index].depth;
|
|
1532
|
+
for (let i = index + 1; i < templates.length; i++) {
|
|
1533
|
+
const next = templates[i];
|
|
1534
|
+
if (next.depth > selfDepth) continue;
|
|
1535
|
+
if (next.depth < selfDepth) return false;
|
|
1536
|
+
if (next.type === "elseif" || next.type === "else") return true;
|
|
1537
|
+
if (next.type === "if") return false;
|
|
1538
|
+
}
|
|
1539
|
+
return false;
|
|
1540
|
+
}
|
|
1541
|
+
function getExpectedType(property, isNegatedIf) {
|
|
1356
1542
|
const prop = property.replace(/#.*$/, "");
|
|
1357
1543
|
if (prop === "for") {
|
|
1358
1544
|
return { label: "for", expected: "array", severity: "error" };
|
|
1359
1545
|
}
|
|
1360
1546
|
if (prop === "if" || prop === "elseif") {
|
|
1547
|
+
if (!isNegatedIf()) return null;
|
|
1361
1548
|
return { label: prop, expected: "boolean", severity: "warning" };
|
|
1362
1549
|
}
|
|
1363
1550
|
if (prop.startsWith("class.")) {
|
|
@@ -1696,20 +1883,18 @@ function findAllCommentBindings(html, commentTextPrefix = "wcs-text") {
|
|
|
1696
1883
|
return results;
|
|
1697
1884
|
}
|
|
1698
1885
|
function isInsideTag(html, offset, tagName) {
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
let lastOpenEnd = -1;
|
|
1702
|
-
let lastCloseEnd = -1;
|
|
1886
|
+
const tagRegex = new RegExp(`<(/?)${tagName}[\\s>]`, "gi");
|
|
1887
|
+
let depth = 0;
|
|
1703
1888
|
let match;
|
|
1704
|
-
while ((match =
|
|
1705
|
-
if (match.index > offset) break;
|
|
1706
|
-
lastOpenEnd = match.index;
|
|
1707
|
-
}
|
|
1708
|
-
while ((match = closeRegex.exec(html)) !== null) {
|
|
1889
|
+
while ((match = tagRegex.exec(html)) !== null) {
|
|
1709
1890
|
if (match.index > offset) break;
|
|
1710
|
-
|
|
1891
|
+
if (match[1]) {
|
|
1892
|
+
depth = Math.max(0, depth - 1);
|
|
1893
|
+
} else {
|
|
1894
|
+
depth++;
|
|
1895
|
+
}
|
|
1711
1896
|
}
|
|
1712
|
-
return
|
|
1897
|
+
return depth > 0;
|
|
1713
1898
|
}
|
|
1714
1899
|
|
|
1715
1900
|
// src/service/templateSyntaxValidator.ts
|
|
@@ -1779,7 +1964,7 @@ function validateTemplateSyntax(html, stateTagName, bindAttrName = "data-wcs", l
|
|
|
1779
1964
|
if (pathPart.startsWith(".")) {
|
|
1780
1965
|
const forPath = insideFor ? getInnermostForPath(html, item.matchStart, bindAttrName) : null;
|
|
1781
1966
|
if (forPath && !forPath.startsWith(".")) {
|
|
1782
|
-
const expandedPath = `${forPath}.*.${pathPart.slice(1)}`;
|
|
1967
|
+
const expandedPath = pathPart === "." ? `${forPath}.*` : `${forPath}.*.${pathPart.slice(1)}`;
|
|
1783
1968
|
if (!isValidTemplatePath(expandedPath, pathSet, defaultPaths)) {
|
|
1784
1969
|
diagnostics.push({
|
|
1785
1970
|
code: WcsDiagnosticCode.BindingPathMissing,
|
|
@@ -2963,6 +3148,55 @@ function blankJsComments(code) {
|
|
|
2963
3148
|
return code.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length)).replace(/(^|[^:])\/\/[^\n]*/g, (m, pre) => pre + " ".repeat(m.length - pre.length));
|
|
2964
3149
|
}
|
|
2965
3150
|
|
|
3151
|
+
// src/service/watchDeclarationValidator.ts
|
|
3152
|
+
var STATE_NAME_SEPARATOR = "@";
|
|
3153
|
+
function validateWatchDeclarations(html, stateTagName = "wcs-state", locale) {
|
|
3154
|
+
const msgs = getMessages(locale);
|
|
3155
|
+
const out = [];
|
|
3156
|
+
for (const block of parseWcsScriptBlocks(html, stateTagName)) {
|
|
3157
|
+
const entries = analyzeWatchEntries(block.content);
|
|
3158
|
+
if (entries.length === 0) continue;
|
|
3159
|
+
const paths = analyzeStatePaths(block.content, block.stateName);
|
|
3160
|
+
const pathSet = new Set(paths.map((p) => p.path));
|
|
3161
|
+
for (const entry of entries) {
|
|
3162
|
+
const diagnostic = validateEntry(entry, pathSet, msgs);
|
|
3163
|
+
if (diagnostic === null) continue;
|
|
3164
|
+
out.push({
|
|
3165
|
+
code: diagnostic.code,
|
|
3166
|
+
start: block.contentStart + entry.start,
|
|
3167
|
+
end: block.contentStart + entry.end,
|
|
3168
|
+
message: diagnostic.message,
|
|
3169
|
+
severity: diagnostic.severity
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
return out;
|
|
3174
|
+
}
|
|
3175
|
+
function validateEntry(entry, pathSet, msgs) {
|
|
3176
|
+
const { key } = entry;
|
|
3177
|
+
const invalid = (message) => ({ code: WcsDiagnosticCode.WatchDeclarationInvalid, message, severity: "error" });
|
|
3178
|
+
if (key.includes(STATE_NAME_SEPARATOR)) {
|
|
3179
|
+
return invalid(msgs.watchKeyCrossState(key));
|
|
3180
|
+
}
|
|
3181
|
+
if (key.startsWith("$")) {
|
|
3182
|
+
return invalid(msgs.watchKeyReserved(key));
|
|
3183
|
+
}
|
|
3184
|
+
if (key.split(".").some((segment) => segment.length === 0)) {
|
|
3185
|
+
return invalid(msgs.watchKeyEmptySegment(key));
|
|
3186
|
+
}
|
|
3187
|
+
if (entry.definitelyNotFunction) {
|
|
3188
|
+
return invalid(msgs.watchHandlerNotFunction(key));
|
|
3189
|
+
}
|
|
3190
|
+
if (pathSet.size > 0 && !pathSet.has(key)) {
|
|
3191
|
+
return {
|
|
3192
|
+
code: WcsDiagnosticCode.WatchPathMissing,
|
|
3193
|
+
message: msgs.watchPathMissing(key),
|
|
3194
|
+
severity: "warning"
|
|
3195
|
+
};
|
|
3196
|
+
}
|
|
3197
|
+
return null;
|
|
3198
|
+
}
|
|
3199
|
+
|
|
2966
3200
|
// src/core/validateDocument.ts
|
|
2967
3201
|
function validateDocument(text, options = {}) {
|
|
2968
3202
|
const bindAttribute = options.bindAttribute ?? "data-wcs";
|
|
@@ -2974,6 +3208,7 @@ function validateDocument(text, options = {}) {
|
|
|
2974
3208
|
out.push(...validateIoNodes(text, bindAttribute, stateTagName, locale));
|
|
2975
3209
|
out.push(...validateDocumentEnv(text, locale));
|
|
2976
3210
|
out.push(...validateArrayMutations(text, stateTagName, locale));
|
|
3211
|
+
out.push(...validateWatchDeclarations(text, stateTagName, locale));
|
|
2977
3212
|
for (const d of validateStateTypes(text, stateTagName, locale)) {
|
|
2978
3213
|
out.push({ code: WcsDiagnosticCode.TypeAnnotation, start: d.start, end: d.end, message: d.message, severity: d.severity });
|
|
2979
3214
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wcstack/lint",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "Static-contract validator CLI (wcs-validate) for wcstack data-wcs bindings and wcstack.manifest.json sidecars. Thin npm wrapper around the wcstack-intellisense validator core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|