@vibes.diy/api-svc 14.3.91 → 14.3.93
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/intern/design-repair.js +5 -6
- package/intern/design-repair.js.map +1 -1
- package/intern/instant-build.d.ts +66 -0
- package/intern/instant-build.js +458 -40
- package/intern/instant-build.js.map +1 -1
- package/intern/opens-ast.d.ts +19 -0
- package/intern/opens-ast.js +75 -0
- package/intern/opens-ast.js.map +1 -0
- package/intern/opens-browser-globals.d.ts +1 -0
- package/intern/opens-browser-globals.js +217 -0
- package/intern/opens-browser-globals.js.map +1 -0
- package/intern/opens-check.d.ts +19 -1
- package/intern/opens-check.js +257 -279
- package/intern/opens-check.js.map +1 -1
- package/intern/opens-hooks.d.ts +7 -0
- package/intern/opens-hooks.js +176 -0
- package/intern/opens-hooks.js.map +1 -0
- package/intern/opens-inert.d.ts +10 -0
- package/intern/opens-inert.js +182 -0
- package/intern/opens-inert.js.map +1 -0
- package/intern/opens-scope.d.ts +61 -0
- package/intern/opens-scope.js +380 -0
- package/intern/opens-scope.js.map +1 -0
- package/intern/repair-ladder.d.ts +53 -0
- package/intern/repair-ladder.js +318 -0
- package/intern/repair-ladder.js.map +1 -0
- package/intern/save-build-pass.js +2 -2
- package/intern/save-build-pass.js.map +1 -1
- package/package.json +13 -13
- package/public/handle-prompt-context.js +12 -1
- package/public/handle-prompt-context.js.map +1 -1
- package/public/prompt-section-actions.js +7 -1
- package/public/prompt-section-actions.js.map +1 -1
package/intern/opens-check.js
CHANGED
|
@@ -4,6 +4,11 @@ import acornJsx from "acorn-jsx";
|
|
|
4
4
|
import { transform } from "sucrase";
|
|
5
5
|
import { POUF_ENTRY_EXPORTS } from "@vibes.diy/pouf/exports";
|
|
6
6
|
import { lockedGroupsVersions } from "./grouped-vibe-import-map.js";
|
|
7
|
+
import { asNode, childNodes, columnAt, field, lineAt, nodeAt, nodesAt, stringAt } from "./opens-ast.js";
|
|
8
|
+
import { GENERATED_BROWSER_GLOBALS } from "./opens-browser-globals.js";
|
|
9
|
+
import { checkHooks } from "./opens-hooks.js";
|
|
10
|
+
import { checkInert } from "./opens-inert.js";
|
|
11
|
+
import { hookCallStarts, resolveName, scanScopes } from "./opens-scope.js";
|
|
7
12
|
export const OPENS_ADVISORY_CLASSES = new Set(["unknown-package"]);
|
|
8
13
|
const LOOK_EXPORTS = [
|
|
9
14
|
"Avatar",
|
|
@@ -351,6 +356,85 @@ export const OPENS_MODULE_EXPORTS = {
|
|
|
351
356
|
"@vibes.diy/vibe-runtime": RUNTIME_EXPORTS,
|
|
352
357
|
...Object.fromEntries(importMapEntries().flatMap(([spec, target]) => target === RUNTIME_ALIAS ? [[spec, RUNTIME_EXPORTS]] : [])),
|
|
353
358
|
};
|
|
359
|
+
const OBJECT_MEMBERS = [
|
|
360
|
+
"constructor",
|
|
361
|
+
"hasOwnProperty",
|
|
362
|
+
"isPrototypeOf",
|
|
363
|
+
"propertyIsEnumerable",
|
|
364
|
+
"toLocaleString",
|
|
365
|
+
"toString",
|
|
366
|
+
"valueOf",
|
|
367
|
+
];
|
|
368
|
+
const FUNCTION_MEMBERS = [...OBJECT_MEMBERS, "apply", "bind", "call", "length", "name"];
|
|
369
|
+
const TOAST_MEMBERS = ["error", "info", "success", "warning"];
|
|
370
|
+
const USE_FIREPROOF_RESULT_KEYS = [
|
|
371
|
+
"access",
|
|
372
|
+
"addInterest",
|
|
373
|
+
"attach",
|
|
374
|
+
"clearWriteRejection",
|
|
375
|
+
"database",
|
|
376
|
+
"fromCache",
|
|
377
|
+
"getFromCache",
|
|
378
|
+
"removeInterest",
|
|
379
|
+
"useAllDocs",
|
|
380
|
+
"useChanges",
|
|
381
|
+
"useDocument",
|
|
382
|
+
"useLiveQuery",
|
|
383
|
+
"writeRejection",
|
|
384
|
+
];
|
|
385
|
+
const DATABASE_MEMBERS = [
|
|
386
|
+
"allDocs",
|
|
387
|
+
"applyEphemeral",
|
|
388
|
+
"applyRemote",
|
|
389
|
+
"applySnapshot",
|
|
390
|
+
"broadcastEphemeral",
|
|
391
|
+
"bulk",
|
|
392
|
+
"changes",
|
|
393
|
+
"close",
|
|
394
|
+
"compact",
|
|
395
|
+
"del",
|
|
396
|
+
"dequeuePush",
|
|
397
|
+
"destroy",
|
|
398
|
+
"dropCursor",
|
|
399
|
+
"dropEphemeralPeer",
|
|
400
|
+
"enableEphemeral",
|
|
401
|
+
"evictChannel",
|
|
402
|
+
"foldSeedDocs",
|
|
403
|
+
"get",
|
|
404
|
+
"getCursors",
|
|
405
|
+
"getSyncMeta",
|
|
406
|
+
"initialSyncConfirmed",
|
|
407
|
+
"initialSyncSettled",
|
|
408
|
+
"materializeAllDocs",
|
|
409
|
+
"materializeLive",
|
|
410
|
+
"name",
|
|
411
|
+
"onSettledChange",
|
|
412
|
+
"overwriteFromServer",
|
|
413
|
+
"peekPushQueue",
|
|
414
|
+
"put",
|
|
415
|
+
"query",
|
|
416
|
+
"queryLive",
|
|
417
|
+
"ready",
|
|
418
|
+
"recordAccepted",
|
|
419
|
+
"remove",
|
|
420
|
+
"resubscribe",
|
|
421
|
+
"revertDoc",
|
|
422
|
+
"seedDocs",
|
|
423
|
+
"setCursors",
|
|
424
|
+
"setReadObserver",
|
|
425
|
+
"size",
|
|
426
|
+
"snapshot",
|
|
427
|
+
"subscribe",
|
|
428
|
+
];
|
|
429
|
+
export const OPENS_MEMBER_SHAPES = {
|
|
430
|
+
toast: TOAST_MEMBERS,
|
|
431
|
+
useFireproofResult: USE_FIREPROOF_RESULT_KEYS,
|
|
432
|
+
database: DATABASE_MEMBERS,
|
|
433
|
+
};
|
|
434
|
+
const TOAST_HOMES = new Set(["@vibes.diy/pouf/interactive", "@vibes.diy/pouf"]);
|
|
435
|
+
function isRuntimeSpecifier(spec) {
|
|
436
|
+
return OPENS_MODULE_EXPORTS[spec] === RUNTIME_EXPORTS;
|
|
437
|
+
}
|
|
354
438
|
function isMappedSpecifier(spec) {
|
|
355
439
|
return importMapEntries().some(([key]) => key === spec || (key.endsWith("/") && spec.startsWith(key)));
|
|
356
440
|
}
|
|
@@ -585,268 +669,12 @@ const BROWSER_GLOBALS = new Set([
|
|
|
585
669
|
"webkitSpeechRecognition",
|
|
586
670
|
"Geolocation",
|
|
587
671
|
]);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
function asNode(value) {
|
|
592
|
-
if (typeof value !== "object" || value === null)
|
|
593
|
-
return undefined;
|
|
594
|
-
if ("type" in value && typeof value.type === "string" && "start" in value && typeof value.start === "number") {
|
|
595
|
-
return { ...value, type: value.type, start: value.start };
|
|
596
|
-
}
|
|
597
|
-
return undefined;
|
|
598
|
-
}
|
|
599
|
-
function field(node, key) {
|
|
600
|
-
return Reflect.get(node, key);
|
|
601
|
-
}
|
|
602
|
-
function nodeAt(node, key) {
|
|
603
|
-
return asNode(field(node, key));
|
|
672
|
+
const ALL_BROWSER_GLOBALS = new Set([...BROWSER_GLOBALS, ...GENERATED_BROWSER_GLOBALS]);
|
|
673
|
+
export function isOpensGlobalName(name) {
|
|
674
|
+
return ALL_BROWSER_GLOBALS.has(name);
|
|
604
675
|
}
|
|
605
|
-
function
|
|
606
|
-
|
|
607
|
-
if (Array.isArray(value) === false)
|
|
608
|
-
return [];
|
|
609
|
-
return value.flatMap((item) => {
|
|
610
|
-
const n = asNode(item);
|
|
611
|
-
return n === undefined ? [] : [n];
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
function stringAt(node, key) {
|
|
615
|
-
const value = field(node, key);
|
|
616
|
-
return typeof value === "string" ? value : undefined;
|
|
617
|
-
}
|
|
618
|
-
function isComputed(node) {
|
|
619
|
-
return field(node, "computed") === true;
|
|
620
|
-
}
|
|
621
|
-
const SKIP_KEYS = new Set(["type", "start", "end", "loc", "range", "raw"]);
|
|
622
|
-
function childNodes(node) {
|
|
623
|
-
return Object.keys(node)
|
|
624
|
-
.filter((key) => SKIP_KEYS.has(key) === false)
|
|
625
|
-
.flatMap((key) => {
|
|
626
|
-
const value = field(node, key);
|
|
627
|
-
const items = Array.isArray(value) ? value : [value];
|
|
628
|
-
return items.flatMap((item) => {
|
|
629
|
-
const child = asNode(item);
|
|
630
|
-
return child === undefined ? [] : [{ key, child }];
|
|
631
|
-
});
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
const HOOK_NAME = /^use[A-Z0-9]/;
|
|
635
|
-
function declarePattern(pattern, visit, scan) {
|
|
636
|
-
switch (pattern.type) {
|
|
637
|
-
case "Identifier": {
|
|
638
|
-
const name = stringAt(pattern, "name");
|
|
639
|
-
if (name !== undefined)
|
|
640
|
-
scan.declared.add(name);
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
case "ObjectPattern":
|
|
644
|
-
for (const prop of nodesAt(pattern, "properties")) {
|
|
645
|
-
if (prop.type === "RestElement") {
|
|
646
|
-
const arg = nodeAt(prop, "argument");
|
|
647
|
-
if (arg !== undefined)
|
|
648
|
-
declarePattern(arg, visit, scan);
|
|
649
|
-
continue;
|
|
650
|
-
}
|
|
651
|
-
const key = nodeAt(prop, "key");
|
|
652
|
-
if (key !== undefined && isComputed(prop))
|
|
653
|
-
walk({ node: key, parent: prop, key: "key", depth: visit.depth }, scan);
|
|
654
|
-
const value = nodeAt(prop, "value");
|
|
655
|
-
if (value !== undefined)
|
|
656
|
-
declarePattern(value, visit, scan);
|
|
657
|
-
}
|
|
658
|
-
return;
|
|
659
|
-
case "ArrayPattern":
|
|
660
|
-
for (const element of nodesAt(pattern, "elements"))
|
|
661
|
-
declarePattern(element, visit, scan);
|
|
662
|
-
return;
|
|
663
|
-
case "RestElement": {
|
|
664
|
-
const arg = nodeAt(pattern, "argument");
|
|
665
|
-
if (arg !== undefined)
|
|
666
|
-
declarePattern(arg, visit, scan);
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
case "AssignmentPattern": {
|
|
670
|
-
const left = nodeAt(pattern, "left");
|
|
671
|
-
const right = nodeAt(pattern, "right");
|
|
672
|
-
if (left !== undefined)
|
|
673
|
-
declarePattern(left, visit, scan);
|
|
674
|
-
if (right !== undefined)
|
|
675
|
-
walk({ node: right, parent: pattern, key: "right", depth: visit.depth }, scan);
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
|
-
default:
|
|
679
|
-
walk({ node: pattern, parent: undefined, key: "", depth: visit.depth }, scan);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
function declareId(node, scan) {
|
|
683
|
-
const id = nodeAt(node, "id");
|
|
684
|
-
const name = id === undefined ? undefined : stringAt(id, "name");
|
|
685
|
-
if (name !== undefined)
|
|
686
|
-
scan.declared.add(name);
|
|
687
|
-
}
|
|
688
|
-
function walkFunction(v, scan) {
|
|
689
|
-
declareId(v.node, scan);
|
|
690
|
-
const inner = { depth: v.depth + 1 };
|
|
691
|
-
for (const param of nodesAt(v.node, "params"))
|
|
692
|
-
declarePattern(param, inner, scan);
|
|
693
|
-
const body = nodeAt(v.node, "body");
|
|
694
|
-
if (body !== undefined)
|
|
695
|
-
walk({ node: body, parent: v.node, key: "body", depth: inner.depth }, scan);
|
|
696
|
-
}
|
|
697
|
-
function jsxReference(name) {
|
|
698
|
-
switch (name.type) {
|
|
699
|
-
case "JSXIdentifier": {
|
|
700
|
-
const id = stringAt(name, "name");
|
|
701
|
-
if (id === undefined || /^[a-z]/.test(id) || id.includes("-"))
|
|
702
|
-
return undefined;
|
|
703
|
-
return { name: id, start: name.start };
|
|
704
|
-
}
|
|
705
|
-
case "JSXMemberExpression": {
|
|
706
|
-
let object = nodeAt(name, "object");
|
|
707
|
-
while (object !== undefined && object.type === "JSXMemberExpression")
|
|
708
|
-
object = nodeAt(object, "object");
|
|
709
|
-
const root = object === undefined ? undefined : stringAt(object, "name");
|
|
710
|
-
return object === undefined || root === undefined || root === "this" ? undefined : { name: root, start: object.start };
|
|
711
|
-
}
|
|
712
|
-
default:
|
|
713
|
-
return undefined;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
function isNonReferenceIdentifier(v) {
|
|
717
|
-
const parent = v.parent;
|
|
718
|
-
if (parent === undefined)
|
|
719
|
-
return false;
|
|
720
|
-
switch (true) {
|
|
721
|
-
case parent.type === "MemberExpression" && v.key === "property" && isComputed(parent) === false:
|
|
722
|
-
case (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") &&
|
|
723
|
-
v.key === "key" &&
|
|
724
|
-
isComputed(parent) === false:
|
|
725
|
-
case parent.type === "LabeledStatement" || parent.type === "BreakStatement" || parent.type === "ContinueStatement":
|
|
726
|
-
case parent.type === "ExportSpecifier" && v.key === "exported":
|
|
727
|
-
case parent.type === "MetaProperty":
|
|
728
|
-
return true;
|
|
729
|
-
default:
|
|
730
|
-
return false;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
function walk(v, scan) {
|
|
734
|
-
const node = v.node;
|
|
735
|
-
switch (node.type) {
|
|
736
|
-
case "Identifier": {
|
|
737
|
-
const name = stringAt(node, "name");
|
|
738
|
-
if (name !== undefined && isNonReferenceIdentifier(v) === false)
|
|
739
|
-
scan.refs.push({ name, start: node.start });
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
case "ImportDeclaration":
|
|
743
|
-
case "ImportExpression":
|
|
744
|
-
case "ExportAllDeclaration":
|
|
745
|
-
return;
|
|
746
|
-
case "ExportNamedDeclaration":
|
|
747
|
-
if (nodeAt(node, "source") !== undefined)
|
|
748
|
-
return;
|
|
749
|
-
break;
|
|
750
|
-
case "VariableDeclarator": {
|
|
751
|
-
const id = nodeAt(node, "id");
|
|
752
|
-
if (id !== undefined)
|
|
753
|
-
declarePattern(id, v, scan);
|
|
754
|
-
const init = nodeAt(node, "init");
|
|
755
|
-
if (init !== undefined)
|
|
756
|
-
walk({ node: init, parent: node, key: "init", depth: v.depth }, scan);
|
|
757
|
-
return;
|
|
758
|
-
}
|
|
759
|
-
case "FunctionDeclaration":
|
|
760
|
-
case "FunctionExpression":
|
|
761
|
-
case "ArrowFunctionExpression":
|
|
762
|
-
walkFunction(v, scan);
|
|
763
|
-
return;
|
|
764
|
-
case "ClassDeclaration":
|
|
765
|
-
case "ClassExpression": {
|
|
766
|
-
declareId(node, scan);
|
|
767
|
-
const superClass = nodeAt(node, "superClass");
|
|
768
|
-
if (superClass !== undefined)
|
|
769
|
-
walk({ node: superClass, parent: node, key: "superClass", depth: v.depth }, scan);
|
|
770
|
-
const body = nodeAt(node, "body");
|
|
771
|
-
if (body !== undefined)
|
|
772
|
-
walk({ node: body, parent: node, key: "body", depth: v.depth + 1 }, scan);
|
|
773
|
-
return;
|
|
774
|
-
}
|
|
775
|
-
case "CatchClause": {
|
|
776
|
-
const param = nodeAt(node, "param");
|
|
777
|
-
if (param !== undefined)
|
|
778
|
-
declarePattern(param, v, scan);
|
|
779
|
-
const body = nodeAt(node, "body");
|
|
780
|
-
if (body !== undefined)
|
|
781
|
-
walk({ node: body, parent: node, key: "body", depth: v.depth }, scan);
|
|
782
|
-
return;
|
|
783
|
-
}
|
|
784
|
-
case "UnaryExpression": {
|
|
785
|
-
const arg = nodeAt(node, "argument");
|
|
786
|
-
if (stringAt(node, "operator") === "typeof" && arg !== undefined && arg.type === "Identifier")
|
|
787
|
-
return;
|
|
788
|
-
break;
|
|
789
|
-
}
|
|
790
|
-
case "CallExpression": {
|
|
791
|
-
const callee = nodeAt(node, "callee");
|
|
792
|
-
const hook = callee === undefined ? undefined : hookName(callee);
|
|
793
|
-
if (hook !== undefined && v.depth === 0)
|
|
794
|
-
scan.hookCalls.push({ name: hook, start: node.start });
|
|
795
|
-
break;
|
|
796
|
-
}
|
|
797
|
-
case "JSXElement": {
|
|
798
|
-
const opening = nodeAt(node, "openingElement");
|
|
799
|
-
if (opening !== undefined)
|
|
800
|
-
walk({ node: opening, parent: node, key: "openingElement", depth: v.depth }, scan);
|
|
801
|
-
for (const child of nodesAt(node, "children"))
|
|
802
|
-
walk({ node: child, parent: node, key: "children", depth: v.depth }, scan);
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
case "JSXOpeningElement": {
|
|
806
|
-
const name = nodeAt(node, "name");
|
|
807
|
-
const ref = name === undefined ? undefined : jsxReference(name);
|
|
808
|
-
if (ref !== undefined)
|
|
809
|
-
scan.refs.push(ref);
|
|
810
|
-
for (const attr of nodesAt(node, "attributes"))
|
|
811
|
-
walk({ node: attr, parent: node, key: "attributes", depth: v.depth }, scan);
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
case "JSXAttribute": {
|
|
815
|
-
const value = nodeAt(node, "value");
|
|
816
|
-
if (value !== undefined)
|
|
817
|
-
walk({ node: value, parent: node, key: "value", depth: v.depth }, scan);
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
case "JSXText":
|
|
821
|
-
case "JSXIdentifier":
|
|
822
|
-
case "JSXNamespacedName":
|
|
823
|
-
case "JSXMemberExpression":
|
|
824
|
-
case "JSXClosingElement":
|
|
825
|
-
case "JSXClosingFragment":
|
|
826
|
-
case "JSXOpeningFragment":
|
|
827
|
-
case "Literal":
|
|
828
|
-
case "TemplateElement":
|
|
829
|
-
return;
|
|
830
|
-
default:
|
|
831
|
-
break;
|
|
832
|
-
}
|
|
833
|
-
for (const { key, child } of childNodes(node))
|
|
834
|
-
walk({ node: child, parent: node, key, depth: v.depth }, scan);
|
|
835
|
-
}
|
|
836
|
-
function hookName(callee) {
|
|
837
|
-
switch (callee.type) {
|
|
838
|
-
case "Identifier": {
|
|
839
|
-
const name = stringAt(callee, "name");
|
|
840
|
-
return name !== undefined && HOOK_NAME.test(name) ? name : undefined;
|
|
841
|
-
}
|
|
842
|
-
case "MemberExpression": {
|
|
843
|
-
const property = nodeAt(callee, "property");
|
|
844
|
-
const name = property === undefined || isComputed(callee) ? undefined : stringAt(property, "name");
|
|
845
|
-
return name !== undefined && HOOK_NAME.test(name) ? name : undefined;
|
|
846
|
-
}
|
|
847
|
-
default:
|
|
848
|
-
return undefined;
|
|
849
|
-
}
|
|
676
|
+
function isGlobalName(name) {
|
|
677
|
+
return isOpensGlobalName(name);
|
|
850
678
|
}
|
|
851
679
|
function collectImports(program, scan) {
|
|
852
680
|
for (const statement of nodesAt(program, "body")) {
|
|
@@ -858,10 +686,6 @@ function collectImports(program, scan) {
|
|
|
858
686
|
case "ImportDeclaration": {
|
|
859
687
|
const names = [];
|
|
860
688
|
for (const specifier of nodesAt(statement, "specifiers")) {
|
|
861
|
-
const local = nodeAt(specifier, "local");
|
|
862
|
-
const localName = local === undefined ? undefined : stringAt(local, "name");
|
|
863
|
-
if (localName !== undefined)
|
|
864
|
-
scan.declared.add(localName);
|
|
865
689
|
const imported = nodeAt(specifier, "imported");
|
|
866
690
|
const importedName = imported === undefined ? undefined : (stringAt(imported, "name") ?? stringAt(imported, "value"));
|
|
867
691
|
if (specifier.type === "ImportSpecifier" && importedName !== undefined)
|
|
@@ -899,9 +723,6 @@ function collectDynamicImports(node, scan) {
|
|
|
899
723
|
for (const { child } of childNodes(node))
|
|
900
724
|
collectDynamicImports(child, scan);
|
|
901
725
|
}
|
|
902
|
-
function lineAt(source, offset) {
|
|
903
|
-
return source.slice(0, offset).split("\n").length;
|
|
904
|
-
}
|
|
905
726
|
function resolveRelative(from, spec) {
|
|
906
727
|
const base = spec.startsWith("/") ? [] : from.split("/").slice(1, -1);
|
|
907
728
|
const parts = [...base];
|
|
@@ -963,10 +784,10 @@ function checkFile(args) {
|
|
|
963
784
|
const program = asNode(parsed.Ok());
|
|
964
785
|
if (program === undefined)
|
|
965
786
|
return NOTHING;
|
|
966
|
-
const scan = {
|
|
787
|
+
const scan = { imports: [] };
|
|
967
788
|
collectImports(program, scan);
|
|
968
789
|
collectDynamicImports(program, scan);
|
|
969
|
-
|
|
790
|
+
const scopes = scanScopes(program);
|
|
970
791
|
const findings = [];
|
|
971
792
|
const deps = [];
|
|
972
793
|
for (const imp of scan.imports) {
|
|
@@ -1012,27 +833,147 @@ function checkFile(args) {
|
|
|
1012
833
|
}
|
|
1013
834
|
}
|
|
1014
835
|
const reported = new Set();
|
|
1015
|
-
for (const ref of
|
|
1016
|
-
if (
|
|
836
|
+
for (const ref of scopes.refs) {
|
|
837
|
+
if (isGlobalName(ref.name) || resolveName(ref.name, ref.scope) !== undefined)
|
|
838
|
+
continue;
|
|
839
|
+
const declaredAt = scopes.declaredAnywhere.get(ref.name);
|
|
840
|
+
const where = { file: path, line: lineAt(source, ref.start), column: columnAt(source, ref.start), name: ref.name };
|
|
841
|
+
if (declaredAt === undefined) {
|
|
842
|
+
if (reported.has(ref.name))
|
|
843
|
+
continue;
|
|
844
|
+
reported.add(ref.name);
|
|
845
|
+
findings.push({ class: "undefined-identifier", message: `${ref.name} is used but never imported or declared`, ...where });
|
|
1017
846
|
continue;
|
|
1018
|
-
|
|
847
|
+
}
|
|
1019
848
|
findings.push({
|
|
1020
|
-
class: "
|
|
1021
|
-
message: `${ref.name} is used but
|
|
1022
|
-
|
|
1023
|
-
line: lineAt(source, ref.start),
|
|
849
|
+
class: "out-of-scope-identifier",
|
|
850
|
+
message: `${ref.name} is used here but declared only inside another function or block (line ${lineAt(source, declaredAt)}), so it is not defined at this line; declare it where every use can see it`,
|
|
851
|
+
...where,
|
|
1024
852
|
});
|
|
1025
853
|
}
|
|
1026
|
-
|
|
854
|
+
const hookCalls = hookCallStarts(scopes);
|
|
855
|
+
for (const call of scopes.calls) {
|
|
856
|
+
if (call.atModule === false || hookCalls.has(call.start) === false)
|
|
857
|
+
continue;
|
|
858
|
+
const name = call.callee.kind === "id" ? call.callee.name : call.callee.property;
|
|
1027
859
|
findings.push({
|
|
1028
860
|
class: "hook-at-module-scope",
|
|
1029
|
-
message: `${
|
|
861
|
+
message: `${name}(…) is called at module scope; hooks run inside a component or another hook`,
|
|
862
|
+
file: path,
|
|
863
|
+
line: lineAt(source, call.start),
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
for (const hook of checkHooks(program, (start) => hookCalls.has(start))) {
|
|
867
|
+
findings.push({
|
|
868
|
+
class: hook.class,
|
|
869
|
+
message: hook.message,
|
|
1030
870
|
file: path,
|
|
1031
871
|
line: lineAt(source, hook.start),
|
|
872
|
+
column: columnAt(source, hook.start),
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
for (const member of memberFindings(scopes)) {
|
|
876
|
+
findings.push({
|
|
877
|
+
class: "unknown-member",
|
|
878
|
+
message: member.message,
|
|
879
|
+
file: path,
|
|
880
|
+
line: lineAt(source, member.nameStart),
|
|
881
|
+
column: columnAt(source, member.nameStart),
|
|
882
|
+
name: member.name,
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
for (const inert of checkInert(program, (offset) => lineAt(source, offset))) {
|
|
886
|
+
findings.push({
|
|
887
|
+
class: inert.class,
|
|
888
|
+
message: inert.message,
|
|
889
|
+
file: path,
|
|
890
|
+
line: lineAt(source, inert.start),
|
|
891
|
+
column: columnAt(source, inert.start),
|
|
892
|
+
...(inert.anchorStart === undefined ? {} : { anchorLine: lineAt(source, inert.anchorStart) }),
|
|
1032
893
|
});
|
|
1033
894
|
}
|
|
1034
895
|
return { findings: findings.sort((a, b) => a.line - b.line), deps };
|
|
1035
896
|
}
|
|
897
|
+
function importedFrom(binding, spec, name) {
|
|
898
|
+
return binding?.origin?.kind === "import" && binding.origin.imported === name && spec(binding.origin.spec);
|
|
899
|
+
}
|
|
900
|
+
function shapeOf(binding) {
|
|
901
|
+
const origin = binding?.origin;
|
|
902
|
+
if (origin === undefined)
|
|
903
|
+
return undefined;
|
|
904
|
+
if (importedFrom(binding, (s) => TOAST_HOMES.has(s), "toast"))
|
|
905
|
+
return "toast";
|
|
906
|
+
if (origin.kind !== "call")
|
|
907
|
+
return undefined;
|
|
908
|
+
if (importedFrom(resolveName(origin.callee, origin.scope), isRuntimeSpecifier, "useFireproof") === false)
|
|
909
|
+
return undefined;
|
|
910
|
+
if (origin.key === undefined)
|
|
911
|
+
return "useFireproofResult";
|
|
912
|
+
return origin.key === "database" ? "database" : undefined;
|
|
913
|
+
}
|
|
914
|
+
const SHAPE_ALLOWED = {
|
|
915
|
+
toast: new Set([...TOAST_MEMBERS, ...FUNCTION_MEMBERS]),
|
|
916
|
+
useFireproofResult: new Set([...USE_FIREPROOF_RESULT_KEYS, ...OBJECT_MEMBERS]),
|
|
917
|
+
database: new Set([...DATABASE_MEMBERS, ...OBJECT_MEMBERS]),
|
|
918
|
+
};
|
|
919
|
+
function shapeMessage(shape, read, member) {
|
|
920
|
+
switch (shape) {
|
|
921
|
+
case "toast":
|
|
922
|
+
return `${read}.${member} is not a function: toast has ${TOAST_MEMBERS.join(", ")} (or call ${read}("…") itself)`;
|
|
923
|
+
case "useFireproofResult":
|
|
924
|
+
return `useFireproof(…) returns no ${member}; it returns ${USE_FIREPROOF_RESULT_KEYS.join(", ")}`;
|
|
925
|
+
case "database":
|
|
926
|
+
return `${read}.${member} does not exist on the database; use useLiveQuery for lists, or database.put, get, del, query, allDocs, subscribe`;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
function memberFindings(scopes) {
|
|
930
|
+
const out = [];
|
|
931
|
+
const reported = new Set();
|
|
932
|
+
function report(finding) {
|
|
933
|
+
if (reported.has(finding.start))
|
|
934
|
+
return;
|
|
935
|
+
reported.add(finding.start);
|
|
936
|
+
out.push(finding);
|
|
937
|
+
}
|
|
938
|
+
for (const use of scopes.members) {
|
|
939
|
+
if (use.called === false)
|
|
940
|
+
continue;
|
|
941
|
+
const shape = shapeOf(resolveName(use.root, use.scope));
|
|
942
|
+
if (shape === undefined)
|
|
943
|
+
continue;
|
|
944
|
+
const [first, second] = use.path;
|
|
945
|
+
if (use.path.length === 1 && SHAPE_ALLOWED[shape].has(first) === false) {
|
|
946
|
+
report({ message: shapeMessage(shape, use.root, first), start: use.start, name: first, nameStart: use.propStart });
|
|
947
|
+
}
|
|
948
|
+
if (use.path.length === 2 &&
|
|
949
|
+
shape === "useFireproofResult" &&
|
|
950
|
+
first === "database" &&
|
|
951
|
+
SHAPE_ALLOWED.database.has(second) === false) {
|
|
952
|
+
report({
|
|
953
|
+
message: shapeMessage("database", `${use.root}.database`, second),
|
|
954
|
+
start: use.start,
|
|
955
|
+
name: second,
|
|
956
|
+
nameStart: use.propStart,
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
for (const call of scopes.calls) {
|
|
961
|
+
if (call.callee.kind !== "id")
|
|
962
|
+
continue;
|
|
963
|
+
const origin = resolveName(call.callee.name, call.scope)?.origin;
|
|
964
|
+
if (origin?.kind !== "call" || origin.key === undefined || SHAPE_ALLOWED.useFireproofResult.has(origin.key))
|
|
965
|
+
continue;
|
|
966
|
+
if (importedFrom(resolveName(origin.callee, origin.scope), isRuntimeSpecifier, "useFireproof") === false)
|
|
967
|
+
continue;
|
|
968
|
+
report({
|
|
969
|
+
message: shapeMessage("useFireproofResult", "useFireproof", origin.key),
|
|
970
|
+
start: call.start,
|
|
971
|
+
name: call.callee.name,
|
|
972
|
+
nameStart: call.start,
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
return out;
|
|
976
|
+
}
|
|
1036
977
|
function isBrowserModule(path) {
|
|
1037
978
|
return /\.(jsx?|tsx?)$/.test(path) && path.startsWith("/~~") === false && path !== "/access.js" && path !== "/backend.js";
|
|
1038
979
|
}
|
|
@@ -1052,4 +993,41 @@ export function checkOpens(vfs) {
|
|
|
1052
993
|
}
|
|
1053
994
|
return { opens: findings.every((finding) => OPENS_ADVISORY_CLASSES.has(finding.class)), findings };
|
|
1054
995
|
}
|
|
996
|
+
export function collapseOpensFindings(findings) {
|
|
997
|
+
const counts = new Map();
|
|
998
|
+
const keyOf = (f) => (f.name === undefined ? undefined : `${f.class}|${f.file}|${f.name}`);
|
|
999
|
+
for (const f of findings) {
|
|
1000
|
+
const key = keyOf(f);
|
|
1001
|
+
if (key !== undefined)
|
|
1002
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
1003
|
+
}
|
|
1004
|
+
const seen = new Set();
|
|
1005
|
+
const out = [];
|
|
1006
|
+
for (const f of findings) {
|
|
1007
|
+
const key = keyOf(f);
|
|
1008
|
+
if (key === undefined) {
|
|
1009
|
+
out.push(f);
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (seen.has(key))
|
|
1013
|
+
continue;
|
|
1014
|
+
seen.add(key);
|
|
1015
|
+
const n = counts.get(key) ?? 1;
|
|
1016
|
+
out.push(n > 1 ? { ...f, message: `${f.message} (${n} uses)` } : f);
|
|
1017
|
+
}
|
|
1018
|
+
return out;
|
|
1019
|
+
}
|
|
1020
|
+
export function opensFindingKey(finding) {
|
|
1021
|
+
return `${finding.class}|${finding.file}|${finding.message.replace(/\(line \d+\)/g, "(line)").replace(/ \(\d+ uses\)$/, "")}`;
|
|
1022
|
+
}
|
|
1023
|
+
export function opensBaseline(vfs) {
|
|
1024
|
+
if (vfs.size === 0)
|
|
1025
|
+
return new Set();
|
|
1026
|
+
return new Set(checkOpens(vfs)
|
|
1027
|
+
.findings.filter((finding) => OPENS_ADVISORY_CLASSES.has(finding.class) === false)
|
|
1028
|
+
.map(opensFindingKey));
|
|
1029
|
+
}
|
|
1030
|
+
export function newOpensFindings(verdict, baseline) {
|
|
1031
|
+
return verdict.findings.filter((finding) => OPENS_ADVISORY_CLASSES.has(finding.class) === false && baseline.has(opensFindingKey(finding)) === false);
|
|
1032
|
+
}
|
|
1055
1033
|
//# sourceMappingURL=opens-check.js.map
|