@abide/abide 0.117.1 → 0.117.2
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/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/compiler/ShadowMap.ts +1 -1
- package/src/compiler/TypeSession.ts +138 -45
- package/src/compiler/readsIn.ts +90 -2
- package/src/shared/reactive/Reactive.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @abide/abide
|
|
2
2
|
|
|
3
|
+
## 0.117.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ea4e27f: a value opening at a member of a name read by name — `reactive(route.url.searchParams.get('book') ?? '')`, `reactive(name.toUpperCase())` — is now a `Reactive` the file reads and writes by name, where before `abide check` refused every write as a string assigned to the `Reactive` and the build rebound the name instead of calling `set`
|
|
8
|
+
- ea4e27f: a value a `refresh({ tags })` marked while nothing read it loads when the next page adopts it — `reactive(() => getMeetings())` on a page visited after the write sends the request and serves the new answer, where it served the answer held before the write and asked nothing (RULEBOOK 13.7)
|
|
9
|
+
|
|
3
10
|
## 0.117.1
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -167,7 +167,7 @@ function copiedFrom(
|
|
|
167
167
|
* markup, or a read the lowering rewrote. Runs are ascending by where they stand, not by where they
|
|
168
168
|
* came from, so this is a scan.
|
|
169
169
|
*/
|
|
170
|
-
function mapForward(
|
|
170
|
+
export function mapForward(
|
|
171
171
|
origins: readonly Origin[],
|
|
172
172
|
position: number,
|
|
173
173
|
): number | undefined {
|
|
@@ -38,10 +38,15 @@ import { CHECKED_VALUES } from './Lowering.ts'
|
|
|
38
38
|
import { diagnosisAt, lineOf } from './lineOf.ts'
|
|
39
39
|
import type { AbideFile } from './parseAbide.ts'
|
|
40
40
|
import { projectShadow, type Span } from './projectShadow.ts'
|
|
41
|
-
import { abideIn } from './reactiveTypes.ts'
|
|
41
|
+
import { type Abide, abideIn, isReactiveType } from './reactiveTypes.ts'
|
|
42
42
|
import type { ParsedSource, ReadSource } from './readSources.ts'
|
|
43
43
|
import { readsIn, type ShadowSpans } from './readsIn.ts'
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
CHECKED_SUFFIX,
|
|
46
|
+
mapForward,
|
|
47
|
+
SHADOW_SUFFIX,
|
|
48
|
+
ShadowMap,
|
|
49
|
+
} from './ShadowMap.ts'
|
|
45
50
|
import { SOURCE_SUFFIX } from './splitImports.ts'
|
|
46
51
|
import { type TemplateContext, templateContext } from './templateContext.ts'
|
|
47
52
|
import { templateWarnings } from './templateWarnings.ts'
|
|
@@ -711,56 +716,144 @@ export class TypeSession {
|
|
|
711
716
|
if (queries.every((query) => query === undefined))
|
|
712
717
|
return { answers, warnings }
|
|
713
718
|
|
|
714
|
-
|
|
719
|
+
let project = await this.currentProject(created, changed)
|
|
715
720
|
if (project === undefined) return { answers, warnings }
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
721
|
+
// Per source, its compiler warnings, the bindings the projection could not type
|
|
722
|
+
// (`bindingsOfReads`), and those the lowered program proved a `Reactive`.
|
|
723
|
+
const warned: Diagnosis[][] = sources.map(() => [])
|
|
724
|
+
const suspects: number[][] = sources.map(() => [])
|
|
725
|
+
const held: Set<number>[] = sources.map(() => new Set())
|
|
726
|
+
const answer = async (
|
|
727
|
+
asked: Project,
|
|
728
|
+
abide: Abide,
|
|
729
|
+
at: number,
|
|
730
|
+
): Promise<void> => {
|
|
731
|
+
const query = queries[at]
|
|
732
|
+
if (query === undefined) return
|
|
733
|
+
const source = sources[at] as (typeof sources)[number]
|
|
734
|
+
const sourceFile = await asked.program.getSourceFile(
|
|
735
|
+
query.map.shadowPath,
|
|
736
|
+
)
|
|
737
|
+
// The shadow has to be INSIDE the opened project or the checker never reads it.
|
|
738
|
+
// Refusing loudly rather than answering nothing: no reads is a file whose every
|
|
739
|
+
// name lowers as written, which renders `[object Object]` where a value was due —
|
|
740
|
+
// a wrong page, with nothing about it saying so.
|
|
741
|
+
if (sourceFile === undefined)
|
|
742
|
+
throw new Error(
|
|
743
|
+
`abide: ${source.path} is not inside the project ${this.tsconfig}, so no name in it can be typed (RULEBOOK 31.3). Give the app a tsconfig whose include reaches it.`,
|
|
744
|
+
)
|
|
745
|
+
const read = await readsIn(
|
|
746
|
+
asked.checker,
|
|
747
|
+
abide,
|
|
748
|
+
sourceFile,
|
|
749
|
+
query.map.shadowPath,
|
|
750
|
+
query.spans,
|
|
751
|
+
source.file,
|
|
752
|
+
held[at] as Set<number>,
|
|
753
|
+
)
|
|
754
|
+
answers[at] = read.answer
|
|
755
|
+
suspects[at] = read.suspects
|
|
756
|
+
const placed: Diagnosis[] = []
|
|
757
|
+
for (const warning of read.warnings) {
|
|
758
|
+
const offset = query.map.toSource(
|
|
724
759
|
query.map.shadowPath,
|
|
760
|
+
warning.at,
|
|
761
|
+
)
|
|
762
|
+
if (
|
|
763
|
+
source.text === undefined ||
|
|
764
|
+
!isIgnored(source.text, offset, warning.message)
|
|
725
765
|
)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
766
|
+
placed.push(
|
|
767
|
+
diagnosisAt(
|
|
768
|
+
source.path,
|
|
769
|
+
source.text,
|
|
770
|
+
offset,
|
|
771
|
+
'warning',
|
|
772
|
+
warning.message,
|
|
773
|
+
),
|
|
733
774
|
)
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
775
|
+
}
|
|
776
|
+
warned[at] = placed
|
|
777
|
+
}
|
|
778
|
+
const every = queries.map((_, at) => at)
|
|
779
|
+
const first = project
|
|
780
|
+
const firstAbide = await abideIn(first)
|
|
781
|
+
await Promise.all(every.map((at) => answer(first, firstAbide, at)))
|
|
782
|
+
// 31.1 — A BINDING THE PROJECTION TYPED FROM A `Reactive` IS ASKED OF THE PROGRAM IT RUNS
|
|
783
|
+
// AS: each file holding one is lowered with what it has answered so far, and a binding that
|
|
784
|
+
// is a `Reactive` there is one in the projection too. A binding typed from one that just
|
|
785
|
+
// turned out to be a `Reactive` is asked again, until a round turns none. Nothing is asked,
|
|
786
|
+
// and no second snapshot taken, where no file holds one (`bindingsOfReads`).
|
|
787
|
+
for (;;) {
|
|
788
|
+
const asking = every.filter(
|
|
789
|
+
(at) => (suspects[at] as number[]).length > 0,
|
|
790
|
+
)
|
|
791
|
+
if (asking.length === 0) break
|
|
792
|
+
const loweredCreated: string[] = []
|
|
793
|
+
const loweredChanged: string[] = []
|
|
794
|
+
const lowered = asking.map((at) => {
|
|
795
|
+
const source = sources[at] as (typeof sources)[number]
|
|
796
|
+
const query = queries[at] as { map: ShadowMap }
|
|
797
|
+
const shadow = checkedShadow(
|
|
740
798
|
source.file,
|
|
799
|
+
answers[at] as CheckerAnswer,
|
|
741
800
|
)
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
'warning',
|
|
758
|
-
warning.message,
|
|
759
|
-
),
|
|
760
|
-
)
|
|
801
|
+
this.register(
|
|
802
|
+
query.map.checkedPath,
|
|
803
|
+
shadow.text,
|
|
804
|
+
loweredCreated,
|
|
805
|
+
loweredChanged,
|
|
806
|
+
)
|
|
807
|
+
this.openOutside(
|
|
808
|
+
source.path,
|
|
809
|
+
query.map.checkedPath,
|
|
810
|
+
loweredChanged,
|
|
811
|
+
)
|
|
812
|
+
return {
|
|
813
|
+
at,
|
|
814
|
+
path: query.map.checkedPath,
|
|
815
|
+
origins: shadow.origins,
|
|
761
816
|
}
|
|
762
|
-
})
|
|
763
|
-
|
|
817
|
+
})
|
|
818
|
+
project = await this.currentProject(loweredCreated, loweredChanged)
|
|
819
|
+
if (project === undefined) return { answers, warnings }
|
|
820
|
+
const asked = project
|
|
821
|
+
const abide = await abideIn(asked)
|
|
822
|
+
const turned = new Set<number>()
|
|
823
|
+
await Promise.all(
|
|
824
|
+
lowered.map(async ({ at, path, origins }) => {
|
|
825
|
+
const sourceFile = await asked.program.getSourceFile(path)
|
|
826
|
+
if (sourceFile === undefined) return
|
|
827
|
+
for (const start of suspects[at] as number[]) {
|
|
828
|
+
const position = mapForward(origins, start)
|
|
829
|
+
if (position === undefined) continue
|
|
830
|
+
const symbol = await asked.checker.getSymbolAtLocation(
|
|
831
|
+
getTouchingPropertyName(sourceFile, position),
|
|
832
|
+
)
|
|
833
|
+
const type =
|
|
834
|
+
symbol === undefined
|
|
835
|
+
? undefined
|
|
836
|
+
: await asked.checker.getTypeOfSymbol(symbol)
|
|
837
|
+
if (
|
|
838
|
+
type !== undefined &&
|
|
839
|
+
(await isReactiveType(
|
|
840
|
+
asked.checker,
|
|
841
|
+
abide,
|
|
842
|
+
type,
|
|
843
|
+
false,
|
|
844
|
+
))
|
|
845
|
+
) {
|
|
846
|
+
;(held[at] as Set<number>).add(start)
|
|
847
|
+
turned.add(at)
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}),
|
|
851
|
+
)
|
|
852
|
+
if (turned.size === 0) break
|
|
853
|
+
await Promise.all([...turned].map((at) => answer(asked, abide, at)))
|
|
854
|
+
for (const at of asking) if (!turned.has(at)) suspects[at] = []
|
|
855
|
+
}
|
|
856
|
+
for (const placed of warned) warnings.push(...placed)
|
|
764
857
|
return { answers, warnings }
|
|
765
858
|
}
|
|
766
859
|
|
package/src/compiler/readsIn.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
import {
|
|
29
29
|
type Symbol as CheckedSymbol,
|
|
30
30
|
type Checker,
|
|
31
|
+
SignatureKind,
|
|
31
32
|
SymbolFlags,
|
|
32
33
|
type Type,
|
|
33
34
|
TypeFlags,
|
|
@@ -129,7 +130,9 @@ export async function readsIn(
|
|
|
129
130
|
shadowPath: string,
|
|
130
131
|
spans: ShadowSpans,
|
|
131
132
|
file: AbideFile,
|
|
132
|
-
|
|
133
|
+
/** Where each name the lowered program proved a `Reactive` starts (`bindingsOfReads`). */
|
|
134
|
+
held: ReadonlySet<number>,
|
|
135
|
+
): Promise<{ answer: CheckerAnswer; warnings: Finding[]; suspects: number[] }> {
|
|
133
136
|
// A destructuring pattern binds names this does not see, and that is 31.12's case.
|
|
134
137
|
const names: Node[] = []
|
|
135
138
|
const spelled = new Set<string>()
|
|
@@ -210,6 +213,12 @@ export async function readsIn(
|
|
|
210
213
|
),
|
|
211
214
|
)
|
|
212
215
|
await dropValueHolders(checker, names, reactive)
|
|
216
|
+
for (let at = 0; at < names.length; at += 1)
|
|
217
|
+
if (
|
|
218
|
+
reactive[at] === undefined &&
|
|
219
|
+
held.has((names[at] as Node).getStart())
|
|
220
|
+
)
|
|
221
|
+
reactive[at] = symbols[at]
|
|
213
222
|
// 32.32 — A VALUE WHOSE `reactive(…)` DECLARES `by` keys a `{#for}` over it,
|
|
214
223
|
// wherever the value is declared.
|
|
215
224
|
const byValue = await Promise.all(
|
|
@@ -296,7 +305,86 @@ export async function readsIn(
|
|
|
296
305
|
spans.scripts,
|
|
297
306
|
))
|
|
298
307
|
warnings.push(warning)
|
|
299
|
-
|
|
308
|
+
const answer = new CheckerAnswer(reads, statics)
|
|
309
|
+
return {
|
|
310
|
+
answer,
|
|
311
|
+
warnings,
|
|
312
|
+
suspects: await bindingsOfReads(
|
|
313
|
+
checker,
|
|
314
|
+
abide,
|
|
315
|
+
names,
|
|
316
|
+
reactive,
|
|
317
|
+
answer,
|
|
318
|
+
),
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Where each binding starts whose type the projection cannot vouch for, for the lowered program to
|
|
324
|
+
* answer (`TypeSession.classify`).
|
|
325
|
+
*
|
|
326
|
+
* 31.1 — A BINDING TYPED FROM A READ IS TYPED FROM THE `Reactive` IN THE PROJECTION, and the read is
|
|
327
|
+
* of its value. `reactive(route.url.pathname)` asks a `Reactive<URL>` for a `pathname` it does not
|
|
328
|
+
* declare; the argument is the error type, which `reactive`'s overloads resolve to a `Keyed`, and
|
|
329
|
+
* the binding then lowered as no `Reactive` at all — `bookId = 'y'` rebinding the name and nothing
|
|
330
|
+
* waking. Lowered, the argument is `route.url().pathname` and the binding a `Reactive<string>`.
|
|
331
|
+
*
|
|
332
|
+
* ONLY A CALL THAT CAN HAND BACK A `Reactive` IS ASKED: an un-annotated binding, not one already,
|
|
333
|
+
* initialized by a call holding a read, to a callee one of whose signatures returns a `Reactive`.
|
|
334
|
+
* Anything else is a value however its reads are typed, and asking would cost every page that
|
|
335
|
+
* computes from one a second snapshot.
|
|
336
|
+
*/
|
|
337
|
+
async function bindingsOfReads(
|
|
338
|
+
checker: Checker,
|
|
339
|
+
abide: Abide,
|
|
340
|
+
names: readonly Node[],
|
|
341
|
+
reactive: readonly (CheckedSymbol | undefined)[],
|
|
342
|
+
answer: CheckerAnswer,
|
|
343
|
+
): Promise<number[]> {
|
|
344
|
+
const suspects: number[] = []
|
|
345
|
+
const asked: Promise<void>[] = []
|
|
346
|
+
for (let at = 0; at < names.length; at += 1) {
|
|
347
|
+
const name = names[at] as Node
|
|
348
|
+
const declaration = name.parent
|
|
349
|
+
if (
|
|
350
|
+
reactive[at] !== undefined ||
|
|
351
|
+
!isVariableDeclaration(declaration) ||
|
|
352
|
+
declaration.type !== undefined ||
|
|
353
|
+
declaration.initializer === undefined
|
|
354
|
+
)
|
|
355
|
+
continue
|
|
356
|
+
let call: Node = declaration.initializer
|
|
357
|
+
while (TRANSPARENT.has(call.kind))
|
|
358
|
+
call = (call as unknown as { expression: Node }).expression
|
|
359
|
+
if (
|
|
360
|
+
!isCallExpression(call) ||
|
|
361
|
+
!answer.holdsRead({ start: call.getStart(), end: call.end })
|
|
362
|
+
)
|
|
363
|
+
continue
|
|
364
|
+
const callee = call.expression
|
|
365
|
+
asked.push(
|
|
366
|
+
(async () => {
|
|
367
|
+
const type = await checker.getTypeAtLocation(callee)
|
|
368
|
+
if (type === undefined) return
|
|
369
|
+
for (const signature of await checker.getSignaturesOfType(
|
|
370
|
+
type,
|
|
371
|
+
SignatureKind.Call,
|
|
372
|
+
)) {
|
|
373
|
+
const returned =
|
|
374
|
+
await checker.getReturnTypeOfSignature(signature)
|
|
375
|
+
if (
|
|
376
|
+
returned !== undefined &&
|
|
377
|
+
(await isReactiveType(checker, abide, returned, false))
|
|
378
|
+
) {
|
|
379
|
+
suspects.push(name.getStart())
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
})(),
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
await Promise.all(asked)
|
|
387
|
+
return suspects
|
|
300
388
|
}
|
|
301
389
|
|
|
302
390
|
/**
|
|
@@ -2219,6 +2219,10 @@ function mirror(cell: Cell, inner: Cell): void {
|
|
|
2219
2219
|
// 7.13 through the adoption: the re-settle a rerun of the adopter makes reaches a reload the
|
|
2220
2220
|
// adopted value owes, rather than finding it unservable and giving up.
|
|
2221
2221
|
if (inner.owes !== OWES_NOTHING) settleOwed(inner)
|
|
2222
|
+
// 13.7 THROUGH THE ADOPTION TOO: a sweep's mark on a value nothing read is `DIRTY`, and an
|
|
2223
|
+
// adoption is that value's next read. A mirror reached through a rerun finds it resolved
|
|
2224
|
+
// already; the first one `adopt` runs does not, and copied the held value over the mark.
|
|
2225
|
+
if (inner.state !== CLEAN) resolve(inner)
|
|
2222
2226
|
const adopted = inner.stream
|
|
2223
2227
|
if (adopted !== null) {
|
|
2224
2228
|
forwardStream(cell, inner, adopted)
|