@abide/abide 0.123.2 → 0.123.3

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.
Files changed (94) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/bin/abide.ts +36 -65
  3. package/package.json +1 -1
  4. package/src/compiler/Diagnostics.ts +198 -0
  5. package/src/compiler/KeptChecker.ts +97 -0
  6. package/src/compiler/Lowering.ts +1 -1
  7. package/src/compiler/PositionedRefusal.ts +1 -1
  8. package/src/compiler/TypeSession.ts +62 -85
  9. package/src/compiler/appLayout.ts +28 -20
  10. package/src/compiler/argsDefaultsIn.ts +1 -1
  11. package/src/compiler/checkApp.ts +64 -68
  12. package/src/compiler/checkedShadow.ts +1 -1
  13. package/src/compiler/compileApp.ts +101 -70
  14. package/src/compiler/declaredExports.ts +2 -4
  15. package/src/compiler/emitClient.ts +1 -1
  16. package/src/compiler/formatAbide.ts +2 -2
  17. package/src/compiler/handlersUnder.ts +17 -29
  18. package/src/compiler/lineOf.ts +0 -19
  19. package/src/compiler/matchersOf.ts +3 -3
  20. package/src/compiler/mountsOf.ts +3 -3
  21. package/src/compiler/readSources.ts +16 -18
  22. package/src/compiler/readsIn.ts +1 -1
  23. package/src/compiler/serveLanguage.ts +23 -57
  24. package/src/compiler/sharedModuleWrites.ts +1 -1
  25. package/src/compiler/templateWarnings.ts +1 -2
  26. package/src/compiler/trackingWarnings.ts +1 -1
  27. package/src/server/CLIENT_BUILD.ts +4 -32
  28. package/src/server/RESUME_MILLISECONDS.ts +2 -0
  29. package/src/server/answerRequest.ts +11 -300
  30. package/src/server/answerRpc.ts +12 -54
  31. package/src/server/answerSocket.ts +8 -35
  32. package/src/server/appBuild.ts +377 -0
  33. package/src/server/appPlugins.ts +15 -17
  34. package/src/server/appTree.ts +6 -5
  35. package/src/server/argsContract.ts +209 -0
  36. package/src/server/bridgeRelay.ts +153 -65
  37. package/src/server/claudeCode.ts +30 -127
  38. package/src/server/config.ts +6 -7
  39. package/src/server/createApp.ts +30 -64
  40. package/src/server/devWorker.ts +11 -15
  41. package/src/server/handlerFileMount.ts +40 -0
  42. package/src/server/index.ts +2 -2
  43. package/src/server/packageDirectory.ts +14 -0
  44. package/src/server/preload.ts +3 -2
  45. package/src/server/principal.ts +4 -3
  46. package/src/server/remote.ts +1 -1
  47. package/src/server/runCompiled.ts +1 -1
  48. package/src/server/runDesktop.ts +1 -1
  49. package/src/server/seedBuffer.ts +1 -1
  50. package/src/server/serveDev.ts +24 -58
  51. package/src/server/servedApp.ts +390 -0
  52. package/src/server/signInChanged.ts +20 -0
  53. package/src/server/surfaceOf.ts +2 -1
  54. package/src/server/versionOf.ts +6 -15
  55. package/src/shared/applyPatch.ts +1 -1
  56. package/src/shared/muxFrames.ts +1 -1
  57. package/src/shared/reactive/Keyed.ts +1 -1
  58. package/src/shared/reactive/Reactive.ts +21 -8
  59. package/src/shared/reactive/argsContract.ts +131 -4
  60. package/src/shared/reactive/index.ts +1 -0
  61. package/src/shared/reactive/patchLog.ts +79 -0
  62. package/src/shared/reactive/patchMarks.ts +34 -0
  63. package/src/shared/reactive/recordWrites.ts +11 -14
  64. package/src/shared/reactive/share.ts +4 -3
  65. package/src/shared/rpc.ts +12 -19
  66. package/src/ui/ArmRegion.ts +10 -4
  67. package/src/ui/Level.ts +11 -20
  68. package/src/ui/LevelChain.ts +272 -0
  69. package/src/ui/Router.ts +54 -245
  70. package/src/ui/Scope.ts +17 -3
  71. package/src/ui/TryRegion.ts +22 -23
  72. package/src/ui/bindSpread.ts +2 -4
  73. package/src/ui/listenForRoutes.ts +3 -10
  74. package/src/ui/outboxQueue.ts +21 -3
  75. package/src/ui/reconcile.ts +4 -6
  76. package/src/ui/routing.ts +2 -1
  77. package/src/ui/rows.ts +2 -2
  78. package/src/ui/setAttribute.ts +12 -3
  79. package/src/ui/socketWire.ts +26 -11
  80. package/src/compiler/Finding.ts +0 -2
  81. package/src/compiler/Refusals.ts +0 -65
  82. package/src/compiler/formatDiagnosis.ts +0 -20
  83. package/src/server/appDirectories.ts +0 -9
  84. package/src/server/argsFor.ts +0 -39
  85. package/src/server/buildDirectory.ts +0 -10
  86. package/src/server/coerceArgs.ts +0 -109
  87. package/src/server/derivedArgs.ts +0 -26
  88. package/src/server/runBuild.ts +0 -24
  89. package/src/server/serveBuild.ts +0 -144
  90. package/src/server/testBuild.ts +0 -220
  91. package/src/shared/reactive/FOLLOWED.ts +0 -8
  92. package/src/shared/reactive/PATCHES.ts +0 -11
  93. package/src/shared/reactive/PATCH_SINKS.ts +0 -26
  94. package/src/shared/reactive/canonicalKey.ts +0 -95
@@ -36,9 +36,13 @@ import {
36
36
  moduleConfigShadow,
37
37
  } from './checkedShadow.ts'
38
38
  import { type CheckerAnswer, UNCHECKED } from './CheckerAnswer.ts'
39
- import type { Finding } from './Finding.ts'
40
- import { isIgnored } from './isIgnored.ts'
41
- import { diagnosisAt, lineOf } from './lineOf.ts'
39
+ import {
40
+ byPlace,
41
+ type Diagnosis,
42
+ type Finding,
43
+ placedFindings,
44
+ } from './Diagnostics.ts'
45
+ import { lineOf } from './lineOf.ts'
42
46
  import { CHECKED_VALUES } from './Lowering.ts'
43
47
  import type { AbideFile } from './parseAbide.ts'
44
48
  import { projectShadow, type Span } from './projectShadow.ts'
@@ -104,9 +108,11 @@ export class TypeSession {
104
108
  api: API,
105
109
  tsconfig: string,
106
110
  keyOf: (path: string) => string,
111
+ root: string,
107
112
  ) {
108
113
  this.api = api
109
114
  this.tsconfig = tsconfig
115
+ this.root = root
110
116
  this.opened = `${dirname(tsconfig)}/${SESSION_CONFIG}`
111
117
  this.keyOf = keyOf
112
118
  }
@@ -152,8 +158,8 @@ export class TypeSession {
152
158
  */
153
159
  private stoodIn = new Set<string>()
154
160
 
155
- /** The app's root, real — set by `openChecker`. */
156
- root = ''
161
+ /** The app's root, real. */
162
+ readonly root: string
157
163
 
158
164
  /**
159
165
  * `path` below the app's root, where it lies under it.
@@ -228,8 +234,11 @@ export class TypeSession {
228
234
  return COMPONENT_MODULE
229
235
  }
230
236
 
231
- /** Opens a session on the project `tsconfig` configures. */
232
- static async open(tsconfig: string): Promise<TypeSession> {
237
+ /**
238
+ * Opens a session on the project `tsconfig` configures, for the app at `root` — real, and empty
239
+ * for a session asked about no app's routes.
240
+ */
241
+ static async open(tsconfig: string, root = ''): Promise<TypeSession> {
233
242
  const api = new API({
234
243
  cwd: dirname(tsconfig),
235
244
  fs: {
@@ -261,7 +270,7 @@ export class TypeSession {
261
270
  throw new Error(
262
271
  'abide: the TypeScript API no longer keeps `toPath` where the type session reads it, so a path cannot be keyed as the checker keys it.',
263
272
  )
264
- const session = new TypeSession(api, tsconfig, toPath)
273
+ const session = new TypeSession(api, tsconfig, toPath, root)
265
274
  return session
266
275
  }
267
276
 
@@ -757,27 +766,14 @@ export class TypeSession {
757
766
  )
758
767
  answers[at] = read.answer
759
768
  suspects[at] = read.suspects
760
- const placed: Diagnosis[] = []
761
- for (const warning of read.warnings) {
762
- const offset = query.map.toSource(
763
- query.map.shadowPath,
764
- warning.at,
765
- )
766
- if (
767
- source.text === undefined ||
768
- !isIgnored(source.text, offset, warning.message)
769
- )
770
- placed.push(
771
- diagnosisAt(
772
- source.path,
773
- source.text,
774
- offset,
775
- 'warning',
776
- warning.message,
777
- ),
778
- )
779
- }
780
- warned[at] = placed
769
+ warned[at] = placedFindings(
770
+ source.path,
771
+ source.text,
772
+ read.warnings,
773
+ (offset) => query.map.toSource(query.map.shadowPath, offset),
774
+ 'warning',
775
+ true,
776
+ )
781
777
  }
782
778
  const every = queries.map((_, at) => at)
783
779
  const first = project
@@ -868,11 +864,13 @@ export class TypeSession {
868
864
  * 11.30 — `unkeyed`: every `args` defaulting a member its body's parameter does not, as warnings.
869
865
  * Both over every `.ts` under `within` and each of `sources` as the shadow `readsFor` registered
870
866
  * for it, because a keyed value is declared in a `.ts` as often as in a `.abide` — one walk of
871
- * the files for the two, each finding on its own line of the file an author wrote.
867
+ * the files for the two, each finding on its own line of the file an author wrote. `scripts`
868
+ * as `diagnose` reads it: an editor asking about one page does not walk the app's `.ts`.
872
869
  */
873
870
  async lints(
874
871
  within: string,
875
872
  sources: readonly Omit<ParsedSource, 'quiet'>[],
873
+ scripts = true,
876
874
  ): Promise<{ ambient: Diagnosis[]; unkeyed: Diagnosis[] }> {
877
875
  using _held = this.hold()
878
876
  const ambient: Diagnosis[] = []
@@ -905,10 +903,13 @@ export class TypeSession {
905
903
  // A file of this session's own is one of `sources`' shadows or not walked at all: a
906
904
  // checked module is the lowering's, and placed nowhere an author wrote.
907
905
  const files: string[] = []
908
- for (const path of this.rootsUnder(project, within)) {
909
- const key = this.keyOf(path)
910
- if (authored.has(key) || !this.shadows.has(key)) files.push(path)
911
- }
906
+ if (scripts)
907
+ for (const path of this.rootsUnder(project, within)) {
908
+ const key = this.keyOf(path)
909
+ if (authored.has(key) || !this.shadows.has(key))
910
+ files.push(path)
911
+ }
912
+ else for (const { map } of authored.values()) files.push(map.shadowPath)
912
913
  await Promise.all(
913
914
  files.map(async (path) => {
914
915
  const sourceFile = await project.program.getSourceFile(path)
@@ -928,26 +929,25 @@ export class TypeSession {
928
929
  : source.text
929
930
  const place = (at: number): number =>
930
931
  shadow === undefined ? at : shadow.map.toSource(path, at)
932
+ const named = source?.path ?? path
933
+ const ambientReads: Finding[] = []
931
934
  for (const read of reads)
932
- ambient.push(
933
- diagnosisAt(
934
- source?.path ?? path,
935
- text,
936
- place(read.at),
937
- 'error',
938
- `abide: a \`global\` value's body reads \`${read.ambient}\`, which belongs to the request it was built in — every later request would be answered with this one's. Take it out of the body, or drop \`global\`. RULEBOOK 11.37.`,
939
- ),
940
- )
941
- for (const finding of defaults)
942
- unkeyed.push(
943
- diagnosisAt(
944
- source?.path ?? path,
945
- text,
946
- place(finding.at),
947
- 'warning',
948
- finding.message,
949
- ),
950
- )
935
+ ambientReads.push({
936
+ at: read.at,
937
+ message: `abide: a \`global\` value's body reads \`${read.ambient}\`, which belongs to the request it was built in — every later request would be answered with this one's. Take it out of the body, or drop \`global\`. RULEBOOK 11.37.`,
938
+ })
939
+ ambient.push(
940
+ ...placedFindings(
941
+ named,
942
+ text,
943
+ ambientReads,
944
+ place,
945
+ 'error',
946
+ ),
947
+ )
948
+ unkeyed.push(
949
+ ...placedFindings(named, text, defaults, place, 'warning'),
950
+ )
951
951
  }),
952
952
  )
953
953
  ambient.sort(byPlace)
@@ -1081,16 +1081,15 @@ export class TypeSession {
1081
1081
  })
1082
1082
  if (source.quiet) continue
1083
1083
  // A refusal is at a projection offset (`Lowering.refusals`).
1084
- for (const refusal of refusals)
1085
- refused.push(
1086
- diagnosisAt(
1087
- source.path,
1088
- source.text,
1089
- map.toSource(map.shadowPath, refusal.at),
1090
- 'error',
1091
- refusal.message,
1092
- ),
1093
- )
1084
+ refused.push(
1085
+ ...placedFindings(
1086
+ source.path,
1087
+ source.text,
1088
+ refusals,
1089
+ (at) => map.toSource(map.shadowPath, at),
1090
+ 'error',
1091
+ ),
1092
+ )
1094
1093
  reports.push(
1095
1094
  report(map.checkedPath, source.path, (position) =>
1096
1095
  lineOf(
@@ -1621,20 +1620,6 @@ declare module '*.md' {
1621
1620
  }
1622
1621
  `
1623
1622
 
1624
- /**
1625
- * One thing the checker said, placed in the file the author wrote.
1626
- *
1627
- * 38.3 — line and column from 1.
1628
- */
1629
- export type Diagnosis = {
1630
- path: string
1631
- line: number
1632
- column: number
1633
- code: number
1634
- category: 'error' | 'warning' | 'suggestion' | 'message'
1635
- message: string
1636
- }
1637
-
1638
1623
  /**
1639
1624
  * Where something is in a file the author wrote, line and column from 1.
1640
1625
  *
@@ -1792,14 +1777,6 @@ async function describe(
1792
1777
  return `${keyword} ${name}: ${spelled}`
1793
1778
  }
1794
1779
 
1795
- function byPlace(one: Diagnosis, two: Diagnosis): number {
1796
- return (
1797
- one.path.localeCompare(two.path) ||
1798
- one.line - two.line ||
1799
- one.column - two.column
1800
- )
1801
- }
1802
-
1803
1780
  /** Indexed by the API's `DiagnosticCategory`. */
1804
1781
  const CATEGORIES = ['warning', 'error', 'suggestion', 'message'] as const
1805
1782
 
@@ -16,16 +16,15 @@ import {
16
16
  } from '#shared/routePattern.ts'
17
17
 
18
18
  import { abideImportsOf, componentGraph } from './componentGraph.ts'
19
+ import { type Diagnosis, Diagnostics } from './Diagnostics.ts'
19
20
  import type { HandlerFile } from './handlersUnder.ts'
20
21
  import { hookCalls, hooksIn } from './hookCalls.ts'
21
22
  import { matchersOf, type MountedTree } from './matchersOf.ts'
22
23
  import { mountsOf } from './mountsOf.ts'
23
24
  import { type AbideFile, scriptBodies } from './parseAbide.ts'
24
25
  import { type ParsedSource, parseSources } from './readSources.ts'
25
- import { Refusals } from './Refusals.ts'
26
26
  import { resolverIn } from './resolverIn.ts'
27
27
  import { attributedImports } from './splitImports.ts'
28
- import type { Diagnosis } from './TypeSession.ts'
29
28
 
30
29
  // THE APP, LAID OUT ONCE PER COMPILE OR CHECK: every `.abide` and handler file it holds — its own, a
31
30
  // dependency's it imports, a package's it mounts — each with where it sits and what that makes it
@@ -110,15 +109,15 @@ export type AppLayout = {
110
109
  */
111
110
  export async function appLayout(root: string): Promise<AppLayout> {
112
111
  // COLLECTED, whoever asks: a layout is kept across checks (`AppChecker`), and a build adopts
113
- // what it found (`Refusals.adopt`).
114
- const refusals = new Refusals(root, true)
112
+ // what it found (`Diagnostics.adopt`).
113
+ const refusals = new Diagnostics(root, true)
115
114
  const { abide, handlers, params, modules, published } = appFiles(
116
115
  root,
117
116
  refusals,
118
117
  )
119
118
  await refuseModuleHooks(root, modules, refusals)
120
119
  const resolve = resolverIn()
121
- const unparsed = new Refusals(root, true)
120
+ const unparsed = new Diagnostics(root, true)
122
121
  const own = parseSources(
123
122
  await textsAt(abide.map((path) => `${root}/${path}`)),
124
123
  unparsed,
@@ -140,13 +139,7 @@ export async function appLayout(root: string): Promise<AppLayout> {
140
139
  found.positions.get(source.path) ?? relative,
141
140
  )
142
141
  refusePlace(source, placed, resolve, refusals)
143
- sources.push({
144
- ...source,
145
- ...placed,
146
- paints: paints(source.file),
147
- invoked: invoked.has(source.path),
148
- hooks: hooksIn(scriptBodies(source.file)),
149
- })
142
+ sources.push(placedSource(source, placed, invoked.has(source.path)))
150
143
  }
151
144
  const texts = await textsAt(found.handlers.map((file) => file.path))
152
145
  const placedHandlers: PlacedHandler[] = []
@@ -209,8 +202,8 @@ export async function appLayout(root: string): Promise<AppLayout> {
209
202
  errors,
210
203
  published,
211
204
  addresses: [...addresses].sort(),
212
- refused: refusals.found,
213
- unparsed: unparsed.found,
205
+ refused: refusals.refused,
206
+ unparsed: unparsed.refused,
214
207
  resolve,
215
208
  }
216
209
  }
@@ -250,9 +243,9 @@ export async function withDependencies(
250
243
  parsed: readonly ParsedSource[],
251
244
  own: readonly string[],
252
245
  resolve: (specifier: string, from: string) => string | undefined,
253
- refusals: Refusals,
246
+ refusals: Diagnostics,
254
247
  /** Where what the parser refuses of a file reached here goes, where apart from `refusals`. */
255
- unparsed: Refusals = refusals,
248
+ unparsed: Diagnostics = refusals,
256
249
  ): Promise<{
257
250
  sources: ParsedSource[]
258
251
  handlers: HandlerFile[]
@@ -347,6 +340,21 @@ export async function withDependencies(
347
340
  }
348
341
 
349
342
  /** Whether a page or an error page has anything to paint: a template, or a head. 23.3, 35.14. */
343
+ /** `source` at `placed`, with what its own text says it paints and calls. */
344
+ export function placedSource(
345
+ source: ParsedSource,
346
+ placed: Placement,
347
+ invoked: boolean,
348
+ ): PlacedSource {
349
+ return {
350
+ ...source,
351
+ ...placed,
352
+ paints: paints(source.file),
353
+ invoked,
354
+ hooks: hooksIn(scriptBodies(source.file)),
355
+ }
356
+ }
357
+
350
358
  export function paints(file: AbideFile): boolean {
351
359
  return file.template.length > 0 || file.head.length > 0
352
360
  }
@@ -362,7 +370,7 @@ function refusePlace(
362
370
  source: ParsedSource,
363
371
  placed: Placement,
364
372
  resolve: (specifier: string, from: string) => string | undefined,
365
- refusals: Refusals,
373
+ refusals: Diagnostics,
366
374
  ): void {
367
375
  const { relative, place, kind } = placed
368
376
  if (source.file.servers.length > 0 && place === 'public')
@@ -404,7 +412,7 @@ function refusePlace(
404
412
  async function refuseModuleHooks(
405
413
  root: string,
406
414
  modules: readonly string[],
407
- refusals: Refusals,
415
+ refusals: Diagnostics,
408
416
  ): Promise<void> {
409
417
  const texts = await textsAt(modules.map((module) => `${root}/${module}`))
410
418
  for (let at = 0; at < modules.length; at += 1) {
@@ -428,7 +436,7 @@ async function refuseModuleHooks(
428
436
  function refuseStrayLauncher(
429
437
  sources: readonly PlacedSource[],
430
438
  handlers: readonly PlacedHandler[],
431
- refusals: Refusals,
439
+ refusals: Diagnostics,
432
440
  ): void {
433
441
  const stray = (relative: string): void =>
434
442
  refusals.at(
@@ -457,7 +465,7 @@ function refuseStrayLauncher(
457
465
  */
458
466
  function routesOf(
459
467
  sources: readonly PlacedSource[],
460
- refusals: Refusals,
468
+ refusals: Diagnostics,
461
469
  ): { routes: AppRoute[]; errors: AppRoute[] } {
462
470
  const layouts: { source: string; segments: Segment[] }[] = []
463
471
  const layoutAt = new Map<string, string>()
@@ -17,7 +17,7 @@ import {
17
17
  } from 'typescript/unstable/ast/is'
18
18
  import { type Checker, SymbolFlags, type Type } from 'typescript/unstable/async'
19
19
 
20
- import type { Finding } from './Finding.ts'
20
+ import type { Finding } from './Diagnostics.ts'
21
21
  import { optionNamed, reactiveOptions } from './reactiveOptions.ts'
22
22
 
23
23
  // 11.30 — AN `args` SCHEMA DEFAULTING A MEMBER THE BODY'S PARAMETER DOES NOT. The key is taken before
@@ -1,15 +1,14 @@
1
1
  import { relative } from 'node:path' // bun exposes no path helpers of its own
2
2
 
3
- import { type Placement, placement } from '#server/placement.ts'
3
+ import { placement } from '#server/placement.ts'
4
4
 
5
- import type { PlacedSource } from './appLayout.ts'
6
- import { type AppChecker, compiled } from './compileApp.ts'
5
+ import { type PlacedSource, placedSource } from './appLayout.ts'
6
+ import { type AppChecker, compiled, compiledSources } from './compileApp.ts'
7
7
  import { abideImportsOf } from './componentGraph.ts'
8
+ import { type Diagnosis, Diagnostics } from './Diagnostics.ts'
8
9
  import { hooksIn } from './hookCalls.ts'
9
10
  import { parseAbide, scriptBodies } from './parseAbide.ts'
10
11
  import { parseSources, readSources } from './readSources.ts'
11
- import { Refusals } from './Refusals.ts'
12
- import type { Diagnosis } from './TypeSession.ts'
13
12
 
14
13
  /**
15
14
  * Every finding `abide check` reports for the app at `appRoot`.
@@ -50,74 +49,71 @@ export async function checkSource(
50
49
  ): Promise<Diagnosis[]> {
51
50
  const { session, root } = checker
52
51
  const layout = await checker.layout()
52
+ const diagnostics = new Diagnostics(root, true)
53
53
  // 38.33 — WHAT THE TREE REFUSES OF IT, off the layout the checker keeps until a move is heard.
54
54
  // What the parser refuses is the held text's, below, never the disk's (`layout.unparsed`).
55
- const tree: Diagnosis[] = []
56
- for (const refused of layout.refused)
57
- if (refused.path === path) tree.push(refused)
58
- const refusals = new Refusals(root, true)
59
- const [page] = parseSources([{ path, text }], refusals)
60
- if (page === undefined) return [...tree, ...refusals.found]
55
+ diagnostics.adopt(layout.refused)
56
+ const [page] = parseSources([{ path, text }], diagnostics)
61
57
  // 23.19 — WHERE THE APP PLACES IT, so a mounted page's `route.params` is typed by the route its
62
58
  // mount gives it, as `checkApp` types it; a file the layout has not seen is placed at its path.
63
- const placed: Pick<Placement, 'kind' | 'route'> =
64
- layout.sources.find((source) => source.path === path) ??
65
- placement(relative(root, path))
66
- const imports: { path: string; text: string; quiet: boolean }[] = []
67
- for (const imported of abideImportsOf(
68
- path,
69
- page.file,
70
- layout.resolve,
71
- ).values())
72
- try {
73
- imports.push({
74
- path: imported,
75
- text: held(imported) ?? (await Bun.file(imported).text()),
76
- quiet: true,
77
- })
78
- } catch {
79
- // A component that cannot be read takes anything here.
80
- }
81
- // 27.13 — AND EVERY FILE FILING `onConfig`, quietly, so a page that only reads `config()` is
82
- // typed by defaults it never imported — as `checkApp`'s whole-app program types it. The calls
83
- // only name candidates; the checker decides which file.
84
- const lowered = new Set(imports.map((one) => one.path))
85
- for (const other of layout.sources) {
86
- if (other.path === path || lowered.has(other.path)) continue
87
- const editing = held(other.path)
88
- if (
89
- editing === undefined || editing === other.text
90
- ? other.hooks.includes('onConfig')
91
- : filesConfig(editing)
59
+ const own: PlacedSource[] = []
60
+ const beside: { path: string; text: string; quiet: boolean }[] = []
61
+ if (page !== undefined) {
62
+ const laid = layout.sources.find((source) => source.path === path)
63
+ own.push(
64
+ laid === undefined
65
+ ? placedSource(page, placement(relative(root, path)), false)
66
+ : { ...laid, ...page },
92
67
  )
93
- imports.push({
94
- path: other.path,
95
- text: editing ?? other.text,
96
- quiet: true,
97
- })
68
+ for (const imported of abideImportsOf(
69
+ path,
70
+ page.file,
71
+ layout.resolve,
72
+ ).values())
73
+ try {
74
+ beside.push({
75
+ path: imported,
76
+ text: held(imported) ?? (await Bun.file(imported).text()),
77
+ quiet: true,
78
+ })
79
+ } catch {
80
+ // A component that cannot be read takes anything here.
81
+ }
82
+ // 27.13 — AND EVERY FILE FILING `onConfig`, quietly, so a page that only reads `config()`
83
+ // is typed by defaults it never imported — as `checkApp`'s whole-app program types it. The
84
+ // calls only name candidates; the checker decides which file.
85
+ const lowered = new Set(beside.map((one) => one.path))
86
+ for (const other of layout.sources) {
87
+ if (other.path === path || lowered.has(other.path)) continue
88
+ const editing = held(other.path)
89
+ if (
90
+ editing === undefined || editing === other.text
91
+ ? other.hooks.includes('onConfig')
92
+ : filesConfig(editing)
93
+ )
94
+ beside.push({
95
+ path: other.path,
96
+ text: editing ?? other.text,
97
+ quiet: true,
98
+ })
99
+ }
98
100
  }
99
101
  // A component that does not parse, or that the checker refuses, is its own file's finding and
100
102
  // takes anything here: a quiet source's refusal is reported by nobody.
101
- const { read, warnings } = await readSources(
102
- session,
103
- [page, ...parseSources(imports, refusals)],
104
- refusals,
105
- )
106
- if (refusals.found.length > 0) return [...tree, ...refusals.found]
107
- const own: Diagnosis[] = []
108
- for (const warning of warnings) if (warning.path === path) own.push(warning)
109
- const [checked, ...beside] = read
110
- const { diagnoses } = await session.diagnose(
111
- checked === undefined
112
- ? beside
113
- : [
114
- { ...checked, kind: placed.kind, route: placed.route },
115
- ...beside,
116
- ],
117
- layout,
118
- false,
119
- )
120
- return [...tree, ...own, ...diagnoses]
103
+ if (own.length > 0)
104
+ await compiledSources(
105
+ layout,
106
+ session,
107
+ own,
108
+ parseSources(beside, diagnostics),
109
+ diagnostics,
110
+ true,
111
+ false,
112
+ )
113
+ const found: Diagnosis[] = []
114
+ for (const finding of diagnostics.findings)
115
+ if (finding.path === path) found.push(finding)
116
+ return found
121
117
  }
122
118
 
123
119
  /**
@@ -132,7 +128,7 @@ export async function lowerApp(
132
128
  held: (path: string) => string | undefined,
133
129
  ): Promise<void> {
134
130
  const layout = await checker.layout()
135
- const refusals = new Refusals(checker.root, true)
131
+ const diagnostics = new Diagnostics(checker.root, true)
136
132
  const sources: (PlacedSource & { quiet: true })[] = []
137
133
  for (const source of layout.sources) {
138
134
  const editing = held(source.path)
@@ -142,12 +138,12 @@ export async function lowerApp(
142
138
  }
143
139
  const [parsed] = parseSources(
144
140
  [{ path: source.path, text: editing, quiet: true }],
145
- refusals,
141
+ diagnostics,
146
142
  )
147
143
  if (parsed !== undefined)
148
144
  sources.push({ ...source, ...parsed, quiet: true })
149
145
  }
150
- const { read } = await readSources(checker.session, sources, refusals)
146
+ const read = await readSources(checker.session, sources, diagnostics)
151
147
  await checker.session.diagnose(read, layout, false)
152
148
  }
153
149
 
@@ -1,7 +1,7 @@
1
1
  import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
2
2
 
3
3
  import type { CheckerAnswer } from './CheckerAnswer.ts'
4
- import type { Finding } from './Finding.ts'
4
+ import type { Finding } from './Diagnostics.ts'
5
5
  import { hookCalls } from './hookCalls.ts'
6
6
  import {
7
7
  CHECKED_CONFIG,