@abide/abide 0.102.0 → 0.103.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/CHANGELOG.md +10 -0
- package/bin/abide.ts +31 -28
- package/docs/REGISTRY.md +30 -26
- package/docs/RULEBOOK.md +98 -83
- package/docs/abide.md +771 -522
- package/package.json +2 -1
- package/scaffold/AGENTS.md +13 -6
- package/src/compiler/TypeSession.ts +110 -35
- package/src/compiler/appMap.ts +96 -0
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +35 -18
- package/src/compiler/checkedShadow.ts +40 -7
- package/src/compiler/compileApp.ts +228 -70
- package/src/compiler/compiledEntry.ts +10 -6
- package/src/compiler/componentGraph.ts +6 -7
- package/src/compiler/declaredExports.ts +3 -3
- package/src/compiler/emitClient.ts +3 -3
- package/src/compiler/emitComponent.ts +9 -9
- package/src/compiler/emitSource.ts +5 -9
- package/src/compiler/handlersUnder.ts +114 -73
- package/src/compiler/hooksCalled.ts +35 -0
- package/src/compiler/mountsOf.ts +30 -18
- package/src/compiler/parseAbide.ts +12 -12
- package/src/compiler/projectShadow.ts +70 -45
- package/src/compiler/readsIn.ts +2 -2
- package/src/compiler/scaffold.ts +24 -21
- package/src/compiler/serveLanguage.ts +0 -24
- package/src/compiler/sharedModuleWrites.ts +2 -2
- package/src/compiler/splitImports.ts +4 -4
- package/src/compiler/writeDefaultProject.ts +28 -1
- package/src/server/MODULE_QUERIES.ts +12 -3
- package/src/server/abideModules.ts +117 -51
- package/src/server/answerRequest.ts +9 -4
- package/src/server/answerRpc.ts +18 -16
- package/src/server/appTree.ts +83 -24
- package/src/server/commands.ts +5 -4
- package/src/server/componentModules.d.ts +1 -2
- package/src/server/createApp.ts +23 -11
- package/src/server/devWorker.ts +5 -1
- package/src/server/fenceServer.ts +36 -13
- package/src/server/hooks.ts +17 -4
- package/src/server/index.ts +1 -0
- package/src/server/installSourceMaps.ts +6 -1
- package/src/server/launcherOptions.ts +12 -13
- package/src/server/launcherPage.ts +2 -2
- package/src/server/nativeMenu.ts +3 -3
- package/src/server/preload.ts +13 -0
- package/src/server/runDesktop.ts +11 -16
- package/src/server/serveDev.ts +8 -2
- package/src/server/surfaceOf.ts +2 -0
- package/src/server/withAbideSources.ts +1 -1
- package/src/shared/ADDRESSES.ts +3 -0
- package/src/shared/LAUNCHER_FILES.ts +9 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +13 -4
- package/src/shared/index.ts +2 -1
- package/src/{ui → shared}/menuBridge.ts +1 -1
- package/src/shared/menuItem.ts +2 -2
- package/src/shared/onMenu.ts +58 -0
- package/src/{ui → shared}/reportMenu.ts +7 -9
- package/src/shared/routePattern.ts +58 -20
- package/src/shared/rpc.ts +1 -1
- package/src/ui/Router.ts +0 -6
- package/src/compiler/syncTypes.ts +0 -32
- package/src/compiler/writeTypes.ts +0 -82
- package/src/server/LAUNCHER_FILES.ts +0 -9
- /package/scaffold/src/{page.abide → index.abide} +0 -0
- /package/src/{compiler → shared}/SPECIFIERS.ts +0 -0
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
Setup,
|
|
25
25
|
TemplateNode,
|
|
26
26
|
} from './TemplateNode.ts'
|
|
27
|
-
import { tokenize } from './tokenize.ts'
|
|
27
|
+
import { nesting, tokenize } from './tokenize.ts'
|
|
28
28
|
|
|
29
29
|
export type Span = { start: number; end: number }
|
|
30
30
|
|
|
@@ -45,7 +45,7 @@ export type Span = { start: number; end: number }
|
|
|
45
45
|
export type Projection = {
|
|
46
46
|
text: string
|
|
47
47
|
modules: Span
|
|
48
|
-
/** 34.
|
|
48
|
+
/** 34.19 — the `<script server>` bodies, module scope like `modules` and the server's alone. */
|
|
49
49
|
servers: Span
|
|
50
50
|
scripts: Span
|
|
51
51
|
/** Every template position, ascending, each with its kind — what the checker's tree is asked. */
|
|
@@ -95,7 +95,7 @@ export function projectShadow(file: AbideFile): Projection {
|
|
|
95
95
|
|
|
96
96
|
type ImportStatement = ReturnType<typeof importStatements>[number]
|
|
97
97
|
|
|
98
|
-
/** 34.
|
|
98
|
+
/** 34.22 — a binding as two blocks importing the same thing agree on it. */
|
|
99
99
|
function bindingKey(
|
|
100
100
|
statement: ImportStatement,
|
|
101
101
|
binding: ImportBinding,
|
|
@@ -104,7 +104,7 @@ function bindingKey(
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
* 34.
|
|
107
|
+
* 34.22 — what is left of an import once the bindings `drops` answers for are taken out:
|
|
108
108
|
* `undefined` where none is, `''` where every one is, and the statement over the rest otherwise,
|
|
109
109
|
* spelled from its tokens.
|
|
110
110
|
*/
|
|
@@ -163,10 +163,10 @@ function project(file: AbideFile): Projection {
|
|
|
163
163
|
}
|
|
164
164
|
return { start, end: text.length }
|
|
165
165
|
}
|
|
166
|
-
// 34.
|
|
166
|
+
// 34.21 — a block copied as written, save that a `.abide` imported as text names the module
|
|
167
167
|
// holding its text, and loses the attribute bun would otherwise read that module as text by.
|
|
168
168
|
//
|
|
169
|
-
// 34.
|
|
169
|
+
// 34.22 — and a binding a `<script server>` and the file's `<script module>` or `<script>` both
|
|
170
170
|
// import is written once: the server's module holds the other blocks' imports too, and written
|
|
171
171
|
// twice it was a duplicate binding. `drops` says which copy goes. A blank line per line it
|
|
172
172
|
// spanned keeps the block line for line with the file.
|
|
@@ -215,7 +215,7 @@ function project(file: AbideFile): Projection {
|
|
|
215
215
|
origin === undefined ? undefined : origin + from,
|
|
216
216
|
)
|
|
217
217
|
}
|
|
218
|
-
// 34.
|
|
218
|
+
// 34.22 — per binding the blocks both sides run import, whether one of them imports a value;
|
|
219
219
|
// and the bindings the server block imports as values.
|
|
220
220
|
const shared = new Map<string, boolean>()
|
|
221
221
|
for (const body of [...file.modules, ...file.scripts])
|
|
@@ -568,11 +568,19 @@ function project(file: AbideFile): Projection {
|
|
|
568
568
|
// 34.3 — BOTH BLOCKS. A `<script>` is a function body on both sides, where an `await` at its top
|
|
569
569
|
// does not parse; a `<script module>` is module scope, where it would, and holds the import of
|
|
570
570
|
// every file that reaches it until it settles — the render waiting on it in all but name.
|
|
571
|
-
|
|
572
|
-
|
|
571
|
+
const modulesText = file.modules.join('\n')
|
|
572
|
+
const serversText = file.servers.join('\n')
|
|
573
|
+
refuseTopLevelAwait(modulesText, '<script module>')
|
|
574
|
+
refuseTopLevelAwait(serversText, '<script server>')
|
|
573
575
|
refuseTopLevelAwait(scriptsText, '<script>')
|
|
574
|
-
|
|
575
|
-
|
|
576
|
+
// A kind at a time: a token at the end of one kind's text would decide how the next one's first
|
|
577
|
+
// reads.
|
|
578
|
+
refuseExport(modulesText)
|
|
579
|
+
refuseExport(serversText)
|
|
580
|
+
refuseExport(scriptsText)
|
|
581
|
+
// 16.70 — what the server block binds, re-exported under a name no author writes, so the
|
|
582
|
+
// checker reads each one's declaring call as it reads a handler file's default.
|
|
583
|
+
text += privateExports(serversText)
|
|
576
584
|
return {
|
|
577
585
|
text,
|
|
578
586
|
modules,
|
|
@@ -699,6 +707,7 @@ declare function ${ROWS_OF}<T>(subject: Iterable<T> | AsyncIterable<T> | null |
|
|
|
699
707
|
*/
|
|
700
708
|
function refuseTopLevelAwait(source: string, block: string): void {
|
|
701
709
|
if (!source.includes(AWAIT)) return
|
|
710
|
+
// Brackets alone, not `nesting`: a template's substitution is still the top level here.
|
|
702
711
|
let depth = 0
|
|
703
712
|
for (const token of tokenize(source)) {
|
|
704
713
|
const text = token.text
|
|
@@ -712,35 +721,72 @@ function refuseTopLevelAwait(source: string, block: string): void {
|
|
|
712
721
|
}
|
|
713
722
|
|
|
714
723
|
/**
|
|
715
|
-
* Throws where a
|
|
724
|
+
* Throws where a script block exports.
|
|
716
725
|
*
|
|
717
|
-
* 34.
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
* nor the fix.
|
|
726
|
+
* 34.17 — A `.abide`'S ONE EXPORT IS ITS COMPONENT. A handler another file calls is a handler file
|
|
727
|
+
* (16.2), a value two files share is a module under a lib directory, and what the framework reads
|
|
728
|
+
* off a file is a hook it calls (37.1). Refused rather than emitted, as 34.3's `await` is: the
|
|
729
|
+
* bundler's own error names neither the file nor the fix.
|
|
721
730
|
*/
|
|
722
|
-
function
|
|
731
|
+
function refuseExport(source: string): void {
|
|
723
732
|
if (!source.includes(EXPORT)) return
|
|
724
733
|
let depth = 0
|
|
725
734
|
let previous = ''
|
|
726
735
|
for (const token of tokenize(source)) {
|
|
727
736
|
const text = token.text
|
|
728
|
-
|
|
729
|
-
|
|
737
|
+
const moved = nesting(token.kind)
|
|
738
|
+
if (moved !== 0) depth += moved
|
|
730
739
|
else if (depth === 0 && text === EXPORT && previous !== '.')
|
|
731
740
|
throw new Error(
|
|
732
|
-
|
|
741
|
+
'abide: a .abide cannot export — its component is its one export. A handler another file calls is a handler file (a .ts beside the page, `export default GET(…)`); a value files share is a module under a lib directory. RULEBOOK 34.17.',
|
|
733
742
|
)
|
|
734
743
|
previous = text
|
|
735
744
|
}
|
|
736
745
|
}
|
|
737
746
|
|
|
747
|
+
/** The prefix a server block's bindings are re-exported under, for the build alone (16.70). */
|
|
748
|
+
export const PRIVATE_EXPORT = '__handler_'
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* 16.70 — the `export { … }` re-exporting each name the server blocks bind under `PRIVATE_EXPORT`,
|
|
752
|
+
* or `''` where they bind none. One spelling for the checker's view and the emitted module, which
|
|
753
|
+
* the build's handler table reads a private handler out of by the same name.
|
|
754
|
+
*/
|
|
755
|
+
export function privateExports(servers: string): string {
|
|
756
|
+
const listed: string[] = []
|
|
757
|
+
for (const name of serverBindings(servers))
|
|
758
|
+
listed.push(`${name} as ${PRIVATE_EXPORT}${name}`)
|
|
759
|
+
return listed.length === 0 ? '' : `export { ${listed.join(', ')} }\n`
|
|
760
|
+
}
|
|
761
|
+
|
|
738
762
|
/**
|
|
739
|
-
*
|
|
740
|
-
*
|
|
763
|
+
* The names a `<script server>` binds with `const`, `let` or `var` at its top level, a
|
|
764
|
+
* destructuring's aside: a handler is a call bound to one name.
|
|
741
765
|
*/
|
|
766
|
+
function serverBindings(source: string): string[] {
|
|
767
|
+
const names: string[] = []
|
|
768
|
+
if (source === '') return names
|
|
769
|
+
let depth = 0
|
|
770
|
+
let declaring = false
|
|
771
|
+
for (const token of tokenize(source)) {
|
|
772
|
+
const text = token.text
|
|
773
|
+
const moved = nesting(token.kind)
|
|
774
|
+
if (moved !== 0) {
|
|
775
|
+
depth += moved
|
|
776
|
+
declaring = false
|
|
777
|
+
} else if (depth !== 0) continue
|
|
778
|
+
else if (text === 'const' || text === 'let' || text === 'var')
|
|
779
|
+
declaring = true
|
|
780
|
+
else if (declaring && token.kind === SyntaxKind.Identifier) {
|
|
781
|
+
names.push(text)
|
|
782
|
+
declaring = false
|
|
783
|
+
} else declaring = false
|
|
784
|
+
}
|
|
785
|
+
return names
|
|
786
|
+
}
|
|
787
|
+
|
|
742
788
|
/**
|
|
743
|
-
* 34.
|
|
789
|
+
* 34.22 — throws where the binding both blocks import is a `.abide`'s default or namespace: the
|
|
744
790
|
* server block's is the function binding its props (34.16) and the other block's is the module it
|
|
745
791
|
* compiles to (32.17), and one name in one module scope cannot hold both.
|
|
746
792
|
*/
|
|
@@ -754,29 +800,8 @@ function refuseTwoFaces(
|
|
|
754
800
|
(binding.imported === 'default' || binding.imported === '*')
|
|
755
801
|
)
|
|
756
802
|
throw new Error(
|
|
757
|
-
`abide: <script server> imports ${binding.local} from '${statement.specifier}' as another script block of its file does — a server block's .abide is the function binding its props (34.16), the other block's is its module (32.17), and one name cannot be both. Import it under another name in the <script server>. RULEBOOK 34.
|
|
758
|
-
)
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
function refuseServerDefault(source: string): void {
|
|
762
|
-
if (!source.includes(EXPORT)) return
|
|
763
|
-
let depth = 0
|
|
764
|
-
let previous = ''
|
|
765
|
-
for (const token of tokenize(source)) {
|
|
766
|
-
const text = token.text
|
|
767
|
-
if (text === '{' || text === '(' || text === '[') depth += 1
|
|
768
|
-
else if (text === '}' || text === ')' || text === ']') depth -= 1
|
|
769
|
-
// `export { x as default }` names it too.
|
|
770
|
-
else if (
|
|
771
|
-
text === 'default' &&
|
|
772
|
-
((depth === 0 && previous === EXPORT) ||
|
|
773
|
-
(depth === 1 && previous === 'as'))
|
|
803
|
+
`abide: <script server> imports ${binding.local} from '${statement.specifier}' as another script block of its file does — a server block's .abide is the function binding its props (34.16), the other block's is its module (32.17), and one name cannot be both. Import it under another name in the <script server>. RULEBOOK 34.22.`,
|
|
774
804
|
)
|
|
775
|
-
throw new Error(
|
|
776
|
-
"abide: a <script server> cannot export `default` — the module's default is its component. Export the handler under a name; the name is its address's last segment. RULEBOOK 34.22.",
|
|
777
|
-
)
|
|
778
|
-
previous = text
|
|
779
|
-
}
|
|
780
805
|
}
|
|
781
806
|
|
|
782
807
|
/**
|
package/src/compiler/readsIn.ts
CHANGED
|
@@ -282,7 +282,7 @@ export async function readsIn(
|
|
|
282
282
|
await proveSlots(checker, expressions, reads, statics)
|
|
283
283
|
reads.sort(byPosition)
|
|
284
284
|
const warnings = await warningsFor(checker, located, spans.positions)
|
|
285
|
-
// 34.
|
|
285
|
+
// 34.18.
|
|
286
286
|
for (const warning of await sharedModuleWrites(
|
|
287
287
|
checker,
|
|
288
288
|
shadowPath,
|
|
@@ -694,7 +694,7 @@ const ROUTE_MEMBERS = new Set(['url', 'params', 'name', 'navigating'])
|
|
|
694
694
|
* a name, and this read is spelled as a member.
|
|
695
695
|
*
|
|
696
696
|
* 23.9 — AND `route.params` IN A PAGE OR A LAYOUT IS WHAT THAT FILE'S OWN ROUTE PROMISES. The file
|
|
697
|
-
* is the route: `media/[id]
|
|
697
|
+
* is the route: `media/[id].abide` only ever renders where `id` matched, so its
|
|
698
698
|
* `route.params` has an `id`, and a layout's has its own segments and whatever a page below adds.
|
|
699
699
|
* Only the checker reads that — the running module reads the one `route` every page shares.
|
|
700
700
|
*/
|
package/src/compiler/scaffold.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 38.1 — `abide scaffold <name>`: THE LAYOUT EVERY ABIDE APP HAS, written into `<name>`. There is
|
|
2
|
-
// nothing to choose, because what is scaffolded is a
|
|
3
|
-
//
|
|
2
|
+
// nothing to choose, because what is scaffolded is a root layout and a page — the files the commands
|
|
3
|
+
// read — and the test preload.
|
|
4
4
|
// A file that is already there is left as it is and reported skipped, so a scaffold run over a
|
|
5
5
|
// half-made project finishes it rather than overwriting it.
|
|
6
6
|
|
|
@@ -8,8 +8,8 @@ import { withoutTrailingSlash } from '#server/withoutTrailingSlash.ts'
|
|
|
8
8
|
import abidePackage from '../../package.json' with { type: 'json' }
|
|
9
9
|
import { COMPILER_OPTIONS } from './writeDefaultProject.ts'
|
|
10
10
|
|
|
11
|
-
// THE STARTER'S OWN FILES, as files: a real document, a real page and
|
|
12
|
-
// stand. What depends on the name
|
|
11
|
+
// THE STARTER'S OWN FILES, as files: a real document, a real page and the agent notes, copied as
|
|
12
|
+
// they stand. What depends on the name, or what a packer drops (`filesFor`), is written here.
|
|
13
13
|
const STARTER = Bun.fileURLToPath(new URL('../../scaffold/', import.meta.url))
|
|
14
14
|
|
|
15
15
|
// THE ABIDE THAT WROTE THE PROJECT is the one it depends on. Installed from a registry, that is its
|
|
@@ -22,23 +22,35 @@ const ABIDE_DEPENDENCY = PACKAGE_ROOT.includes('/node_modules/')
|
|
|
22
22
|
? `^${abidePackage.version}`
|
|
23
23
|
: `file:${PACKAGE_ROOT}`
|
|
24
24
|
|
|
25
|
+
// The `#src` alias, written as the package's `imports` and again as the checker's `paths`.
|
|
26
|
+
const SOURCE_ALIAS = { '#src/*': ['./src/*', './src/*.ts'] }
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 38.109 — the starter's `bunfig.toml`: a test imports the app's `.abide` files as modules, with
|
|
30
|
+
* nothing booted first.
|
|
31
|
+
*/
|
|
32
|
+
export const TEST_PRELOAD =
|
|
33
|
+
'# 38.109 — a test imports the app\'s `.abide` files as modules, with nothing booted first.\n[test]\npreload = ["@abide/abide/test"]\n'
|
|
34
|
+
|
|
35
|
+
// WRITTEN, NOT COPIED, beside the manifests: a packer drops a `bunfig.toml` or a `.gitignore` from
|
|
36
|
+
// the tarball (`bun pm pack` both, `npm pack` the second), so neither survives as a starter file.
|
|
25
37
|
function filesFor(name: string): Record<string, string> {
|
|
26
38
|
return {
|
|
39
|
+
'bunfig.toml': TEST_PRELOAD,
|
|
40
|
+
'.gitignore': 'node_modules\n.abide\n',
|
|
27
41
|
'package.json': `${JSON.stringify(
|
|
28
42
|
{
|
|
29
43
|
name,
|
|
30
44
|
private: true,
|
|
31
45
|
type: 'module',
|
|
32
|
-
// 34.24 — `prepare` runs on `bun install`, so a `.ts` importing a `.abide` is typed
|
|
33
|
-
// before any other abide command has run; and never fails it, as it would where
|
|
34
|
-
// `package.json` is installed before `src` is there to sync (38.26).
|
|
35
46
|
scripts: {
|
|
36
|
-
prepare: 'abide sync || true',
|
|
37
47
|
// 38.103 — the checker over `.abide`, which `tsc` does not read.
|
|
38
48
|
check: 'abide check',
|
|
39
49
|
dev: 'abide dev',
|
|
40
50
|
build: 'abide build',
|
|
41
51
|
start: 'abide start',
|
|
52
|
+
// 38.108 — every address, and the file answering it.
|
|
53
|
+
routes: 'abide routes',
|
|
42
54
|
},
|
|
43
55
|
// ONE ALIAS FOR THE SOURCE, and two targets for it, because an extensionless
|
|
44
56
|
// specifier is how an import is written and only bun probes for the extension:
|
|
@@ -47,9 +59,7 @@ function filesFor(name: string): Record<string, string> {
|
|
|
47
59
|
// them (31.3). The first target is what bun resolves, and the one a `.ts` or
|
|
48
60
|
// `.abide` specifier needs. Which side a module is on is its directory's to say
|
|
49
61
|
// (36.10), not its alias's.
|
|
50
|
-
imports:
|
|
51
|
-
'#src/*': ['./src/*', './src/*.ts'],
|
|
52
|
-
},
|
|
62
|
+
imports: SOURCE_ALIAS,
|
|
53
63
|
dependencies: { '@abide/abide': ABIDE_DEPENDENCY },
|
|
54
64
|
// `Bun`, `bun:sqlite` and the rest of the runtime a server module reaches for are
|
|
55
65
|
// typed by these, and `abide check` reports every one untyped without them.
|
|
@@ -60,20 +70,13 @@ function filesFor(name: string): Record<string, string> {
|
|
|
60
70
|
)}\n`,
|
|
61
71
|
'tsconfig.json': `${JSON.stringify(
|
|
62
72
|
// TypeScript 7 includes no `@types` package it is not named, so the runtime's is named.
|
|
63
|
-
// 34.
|
|
64
|
-
//
|
|
73
|
+
// 34.21 — the alias as a path too: the checker resolves a text import's declaration
|
|
74
|
+
// beside the file a path names, and takes an `imports` target as the exact file.
|
|
65
75
|
{
|
|
66
76
|
compilerOptions: {
|
|
67
77
|
...COMPILER_OPTIONS,
|
|
68
78
|
types: ['bun'],
|
|
69
|
-
|
|
70
|
-
paths: {
|
|
71
|
-
'#src/*': [
|
|
72
|
-
'./src/*',
|
|
73
|
-
'./src/*.ts',
|
|
74
|
-
'./.abide/types/src/*',
|
|
75
|
-
],
|
|
76
|
-
},
|
|
79
|
+
paths: SOURCE_ALIAS,
|
|
77
80
|
},
|
|
78
81
|
include: ['src'],
|
|
79
82
|
},
|
|
@@ -4,10 +4,7 @@ import { realOrAsGiven } from '#server/realOrAsGiven.ts'
|
|
|
4
4
|
import { checkSource } from './checkApp.ts'
|
|
5
5
|
import { type AppChecker, openChecker } from './compileApp.ts'
|
|
6
6
|
import { nearest } from './nearest.ts'
|
|
7
|
-
import { parseAbide } from './parseAbide.ts'
|
|
8
|
-
import { syncTypes } from './syncTypes.ts'
|
|
9
7
|
import type { Diagnosis, Inspection, Place } from './TypeSession.ts'
|
|
10
|
-
import { writeTypes } from './writeTypes.ts'
|
|
11
8
|
|
|
12
9
|
// 38.11 — `abide lsp`: THE `.abide` LANGUAGE SERVER, OVER STDIO. It is `abide check` (38.3) asked
|
|
13
10
|
// about one open document at a time, so the squiggle an editor draws and the line CI fails on are
|
|
@@ -197,27 +194,6 @@ export async function serveLanguage(
|
|
|
197
194
|
// A newer text has arrived while this one was checked, and will be published.
|
|
198
195
|
if (documents.get(uri)?.version !== version) return
|
|
199
196
|
publish(uri, found, document.text)
|
|
200
|
-
await typesFor(app, path, document.text)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// 34.24 — THE TYPES A PLAIN `.ts` READS AN OPEN FILE BY, as the editor holds it: the whole app's
|
|
204
|
-
// the first time an app is seen, and the one file as it is edited after that.
|
|
205
|
-
const synced = new Set<string>()
|
|
206
|
-
const typesFor = async (
|
|
207
|
-
app: string,
|
|
208
|
-
path: string,
|
|
209
|
-
text: string,
|
|
210
|
-
): Promise<void> => {
|
|
211
|
-
try {
|
|
212
|
-
if (synced.has(app))
|
|
213
|
-
await writeTypes(app, [{ path, file: parseAbide(text) }], false)
|
|
214
|
-
else {
|
|
215
|
-
synced.add(app)
|
|
216
|
-
await syncTypes(app, new Map([[path, text]]))
|
|
217
|
-
}
|
|
218
|
-
} catch {
|
|
219
|
-
// A file that does not parse keeps the types it last had; its check already says why.
|
|
220
|
-
}
|
|
221
197
|
}
|
|
222
198
|
|
|
223
199
|
const check = (uri: string): void => {
|
|
@@ -11,7 +11,7 @@ import { classifyRead, FUNCTIONS } from './classifyRead.ts'
|
|
|
11
11
|
import type { Finding } from './Finding.ts'
|
|
12
12
|
import type { Span } from './projectShadow.ts'
|
|
13
13
|
|
|
14
|
-
// 34.
|
|
14
|
+
// 34.18 — A PLAIN `let` IN A `<script module>` THAT A `<script>` WRITES AS IT SETS UP. The block is
|
|
15
15
|
// module scope (34.2), which a server shares across every request, and setup runs inside a render —
|
|
16
16
|
// so the value one request leaves is the one the next request starts from, and its markup drifts
|
|
17
17
|
// from what the browser builds. A `reactive` is per request wherever it is declared (11.32), which
|
|
@@ -74,7 +74,7 @@ export async function sharedModuleWrites(
|
|
|
74
74
|
if (insideFunction(node)) continue
|
|
75
75
|
warnings.push({
|
|
76
76
|
at,
|
|
77
|
-
message: `abide: \`${name}\` is a plain \`let\` in <script module>, which a server shares across every request, and this <script> writes it while the page renders — so one request's value is where the next one starts. Declare it with \`reactive\`, which is per request wherever it stands, or move it into <script>. RULEBOOK 34.
|
|
77
|
+
message: `abide: \`${name}\` is a plain \`let\` in <script module>, which a server shares across every request, and this <script> writes it while the page renders — so one request's value is where the next one starts. Declare it with \`reactive\`, which is per request wherever it stands, or move it into <script>. RULEBOOK 34.18.`,
|
|
78
78
|
})
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -263,7 +263,7 @@ export function abideImports(
|
|
|
263
263
|
for (const span of importSpans(script))
|
|
264
264
|
if (
|
|
265
265
|
span.specifier.endsWith('.abide') &&
|
|
266
|
-
// 34.
|
|
266
|
+
// 34.21 — a `.abide` imported as text is its text, and no component edge.
|
|
267
267
|
(span.dynamic ||
|
|
268
268
|
!IMPORT_ATTRIBUTES.text.test(
|
|
269
269
|
script.slice(span.literalEnd, span.end),
|
|
@@ -386,7 +386,7 @@ export function importStatements(script: string): {
|
|
|
386
386
|
return found
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
-
// The attribute a static import is read by: as text (34.
|
|
389
|
+
// The attribute a static import is read by: as text (34.21), or as a mount of routes (23.19).
|
|
390
390
|
export const IMPORT_ATTRIBUTES = {
|
|
391
391
|
text: /\btype\s*:\s*['"]text['"]/,
|
|
392
392
|
routes: /\btype\s*:\s*['"]routes['"]/,
|
|
@@ -396,7 +396,7 @@ export const IMPORT_ATTRIBUTES = {
|
|
|
396
396
|
* Every static import a script makes `with { type }`: the specifier, where the statement and its
|
|
397
397
|
* literal begin, and where the attributes end.
|
|
398
398
|
*
|
|
399
|
-
* 34.
|
|
399
|
+
* 34.21 — a text import's literal-to-end span is what the projection writes over with the specifier
|
|
400
400
|
* the checker types a `string` (`SOURCE_SUFFIX`), and the emitters ask for as text
|
|
401
401
|
* (`facedSpecifier`). 23.19 — a routes import's whole statement is what the projection blanks: a
|
|
402
402
|
* mount is read by the build off the file, and neither bun nor the checker has a module to give it.
|
|
@@ -466,7 +466,7 @@ export function importedNames(
|
|
|
466
466
|
* own path, so bun resolves a relative, a `#` or a bare specifier from the file that wrote it.
|
|
467
467
|
*
|
|
468
468
|
* 32.17 — a component block's `.abide` import is of the module that file compiles to; a server
|
|
469
|
-
* block's stays bare, for the face a module abide did not compile gets (34.16). 34.
|
|
469
|
+
* block's stays bare, for the face a module abide did not compile gets (34.16). 34.21 — a file the
|
|
470
470
|
* projection marked as text is asked for as its text.
|
|
471
471
|
*/
|
|
472
472
|
function facedSpecifier(specifier: string, lane: Lane): string | undefined {
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
import { mkdtemp, rm } from 'node:fs/promises' // bun exposes no temporary-directory creation or recursive removal of its own
|
|
12
12
|
import { tmpdir } from 'node:os' // bun exposes no temporary-directory path of its own
|
|
13
|
+
import { dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
|
|
14
|
+
import { nearest } from './nearest.ts'
|
|
13
15
|
|
|
14
16
|
/** What every abide app in the docs and the scaffold is written against. */
|
|
15
17
|
export const COMPILER_OPTIONS = {
|
|
@@ -31,7 +33,7 @@ export async function writeDefaultProject(
|
|
|
31
33
|
await Bun.write(
|
|
32
34
|
tsconfig,
|
|
33
35
|
JSON.stringify({
|
|
34
|
-
compilerOptions: COMPILER_OPTIONS,
|
|
36
|
+
compilerOptions: { ...COMPILER_OPTIONS, paths: await aliasPaths(root) },
|
|
35
37
|
include: [`${root}/**/*`],
|
|
36
38
|
}),
|
|
37
39
|
)
|
|
@@ -40,3 +42,28 @@ export async function writeDefaultProject(
|
|
|
40
42
|
remove: () => rm(directory, { recursive: true, force: true }),
|
|
41
43
|
}
|
|
42
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The app's `imports` aliases as `paths`, each target absolute.
|
|
48
|
+
*
|
|
49
|
+
* 34.21 — the checker resolves a text import's declaration beside the file a path names, and takes
|
|
50
|
+
* an `imports` target as the exact file, so `#src/x.abide` imported as text is found through these.
|
|
51
|
+
*/
|
|
52
|
+
async function aliasPaths(root: string): Promise<Record<string, string[]>> {
|
|
53
|
+
const paths: Record<string, string[]> = {}
|
|
54
|
+
const manifest = nearest(root, 'package.json')
|
|
55
|
+
if (manifest === undefined) return paths
|
|
56
|
+
const imports = (
|
|
57
|
+
(await Bun.file(manifest).json()) as {
|
|
58
|
+
imports?: Record<string, unknown>
|
|
59
|
+
}
|
|
60
|
+
).imports
|
|
61
|
+
const base = dirname(manifest)
|
|
62
|
+
for (const [key, targets] of Object.entries(imports ?? {})) {
|
|
63
|
+
const resolved: string[] = []
|
|
64
|
+
for (const target of [targets].flat())
|
|
65
|
+
if (typeof target === 'string') resolved.push(resolve(base, target))
|
|
66
|
+
if (resolved.length > 0) paths[key] = resolved
|
|
67
|
+
}
|
|
68
|
+
return paths
|
|
69
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// WHAT AN IMPORT OF A `.abide` ASKS FOR, spelled on the specifier: bun hands a
|
|
2
|
-
// attributes, so the compiler writes the face it wants where the plugin can read it
|
|
1
|
+
// WHAT AN IMPORT OF A `.abide` OR A HANDLER FILE ASKS FOR, spelled on the specifier: bun hands a
|
|
2
|
+
// plugin no import attributes, so the compiler writes the face it wants where the plugin can read it
|
|
3
3
|
// (`abideModules`). A `.abide` named with none is the face a module abide did not compile gets.
|
|
4
4
|
|
|
5
5
|
/** The module the file compiles to, as a compiled module imports it. 32.17. */
|
|
@@ -8,7 +8,7 @@ export const COMPONENT_QUERY = '?component'
|
|
|
8
8
|
/** The file's scoped stylesheet, which its browser module imports. 34.4. */
|
|
9
9
|
export const STYLE_QUERY = '?style'
|
|
10
10
|
|
|
11
|
-
/** The file's text. 34.
|
|
11
|
+
/** The file's text. 34.21. */
|
|
12
12
|
export const TEXT_QUERY = '?text'
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -16,3 +16,12 @@ export const TEXT_QUERY = '?text'
|
|
|
16
16
|
* under a DOM in `bun test`. Its own `.abide` imports are browser modules too.
|
|
17
17
|
*/
|
|
18
18
|
export const CLIENT_QUERY = '?client'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What a handler file is imported as: its own path with this after it, so the plugin can serve it
|
|
22
|
+
* between the calls that file its hooks (37.1) and a browser its wrapper (16.74). A stack frame names
|
|
23
|
+
* the file without it (38.97).
|
|
24
|
+
*/
|
|
25
|
+
export const HANDLER_SUFFIX = '.abide-handler'
|
|
26
|
+
/** A path that is a handler file's face. */
|
|
27
|
+
export const HANDLER_FACE = new RegExp(`${RegExp.escape(HANDLER_SUFFIX)}$`)
|