diff --git a/.llm/plans/manifest-to-ir-migration.md b/.llm/plans/manifest-to-ir-migration.md
new file mode 100644
index 0000000..38bbf50
--- /dev/null
+++ b/.llm/plans/manifest-to-ir-migration.md
@@ -0,0 +1,260 @@
+# Implementation Plan: Manifest → IR Migration
+
+## Status (progress)
+
+Done:
+
+- Centralized the IR→manifest converter as `compiler.ManifestFromIR` and added
+ IR-first validation entrypoints `compiler.ValidateProgram` /
+ `compiler.ValidateBackendBindingPolicyIR`.
+- Extracted shared leaf value types into the neutral `internal/source` package
+ (manifest re-exports them as aliases). `internal/gwdkir`, `internal/gwdkast`,
+ and `internal/contractscan` are now manifest-free (verified via
+ `go list -deps`).
+- Added IR-native `Page` behavior methods (`CachePolicy`, `RenderMode`,
+ `HasGoBlock`, `DynamicParams`, `TypedRouteParams`) so render helpers consume IR.
+- Migrated `internal/buildgen` render helpers to consume `gwdkir` models; only a
+ `gotypes` bridge and public-API compat types remain.
+- Swapped leaf-type references to `internal/source` across `appgen`, `compiler`,
+ `parser`, `gwdkanalysis`, `lsp`.
+
+Pending (largest remaining work):
+
+- Make `internal/compiler` validation/binding IR-native (it still validates the
+ manifest model — ~980 references). Tracked in **issue #145**. This is a
+ redesign, not a mechanical swap: a naive flip to `ValidateProgram(ir)` would
+ silently drop `validateStandaloneEndpoints` / `validateRouteMethodConflicts`
+ because IR lowering is lossy for endpoint spans, route params, and the raw
+ kind/method those validators check. The IR must be enriched first and the flip
+ guarded by a zero-diagnostic-drift differential corpus.
+- Collapse the `AST → manifest → IR` path to `AST → IR` in `gwdkanalysis`.
+- Keep public manifest JSON until a release plan deprecates it.
+
+## Context
+
+`docs/engineering/architecture.md` ("Compatibility Records" section) tracks
+`internal/manifest` compatibility records as known, well-documented debt. The
+`gwdkir.Program` IR is meant to be the single stable compiler handoff, but
+`internal/manifest` types are still threaded through six packages. Reference
+counts of `manifest.` (non-test) today:
+
+| Package | `manifest.` refs | Role |
+| --- | ---: | --- |
+| `internal/compiler` | 1058 | validation, backend binding, route metadata — gates everyone |
+| `internal/buildgen` | 731 | render helpers + an **IR→manifest reconstruction shim** |
+| `internal/appgen` | 214 | route/adapter planning (already IR-first at the boundary) |
+| `internal/gwdkanalysis` | 176 | AST→manifest lowering, then manifest→IR build |
+| `internal/parser` | 169 | emits manifest records beside typed AST |
+| `internal/gwdkir` | 80 | re-exports a few shared manifest value types |
+| `internal/lang` | 41 | CLI parse/report glue |
+| `internal/gwdkast` | 35 | shares span/value types with manifest |
+
+The single most important fact from the data-flow investigation: **manifest is
+a mandatory intermediate, not a parallel output.** The current pipeline is
+`AST → manifest → IR`, and `gwdkir` already models essentially every record
+manifest does (Page, Component, Layout, Action, API, Fragment, StateContract,
+WASMContract, Prop, Export, Emit, spans, BackendBinding-as-`Endpoint.Binding`).
+So this migration is mostly **re-pointing consumers and deleting converters**,
+not designing new models.
+
+## Assumptions
+
+- `gwdkir.Program` stays the stable handoff; new fields are added there first.
+- Public manifest **JSON** output (`internal/manifest/json.go`) is a separate
+ concern and is explicitly OUT of scope here (architecture doc step 5 keeps it
+ until a release plan deprecates it). We migrate the in-memory model coupling,
+ not the wire format.
+- Behavior must not change; each step is independently shippable and gated by
+ the existing test suite.
+- No new third-party dependencies.
+
+## The Two Seams That Matter
+
+The data flow has exactly two conversion seams to delete, in this order:
+
+1. **`buildgen.buildModelFromIR(ir)` — `internal/buildgen/ir.go:8`** (the
+ IR→manifest reconstruction shim). buildgen's public entrypoint
+ `BuildFromIR(config, ir, outputDir)` **already takes IR**, then immediately
+ rebuilds a `manifest.Manifest` from it (`buildPageFromIR`,
+ `buildComponentFromIR`, `buildLayoutFromIR`, `buildBackendBindingFromIR`)
+ solely to call `compiler.ValidateManifest` and
+ `compiler.ValidateBackendBindingPolicy` (see `build.go:37,43`). This is the
+ exact round-trip the architecture doc warns about and the **highest-leverage
+ first kill** — it is lossy, fragile, and serves only validation.
+
+2. **`gwdkanalysis.BuildIR(config, app manifest.Manifest)` —
+ `internal/gwdkanalysis/ir_builder.go:14`** (the manifest→IR build). This is
+ the backbone of the pipeline. Removing manifest as the *input* to IR is the
+ final, largest step and depends on parser emitting AST-first.
+
+## Proposed Changes (sequenced — each step ships independently)
+
+### Step 0 — Guardrails (no behavior change)
+
+- Add a focused golden test that runs the full `cmd/gowdk build` orchestration
+ on a representative fixture and snapshots the resulting `gwdkir.Program`
+ (JSON-serialized) plus generated output file list. This snapshot is the
+ safety net every later step must keep green. Land this BEFORE touching code.
+
+### Step 1 — Make compiler validation IR-native (the keystone)
+
+`compiler` gates every other package, and validation is what forces the
+buildgen IR→manifest shim to exist. Provide IR-typed validation entrypoints
+that read `gwdkir.Program`:
+
+- `compiler.ValidateProgram(config, ir gwdkir.Program) error` — IR twin of
+ `ValidateManifest` (`validate.go:29`).
+- `compiler.ValidateBackendBindingPolicyIR(config, ir gwdkir.Program) error` —
+ IR twin of `ValidateBackendBindingPolicy` (`backend_binding_policy.go:12`).
+
+Implement these by reading IR fields directly. Where a sub-validator
+(`validate_page.go`, `validate_component_*.go`, etc.) reads a manifest field,
+add an IR-reading variant or a tiny accessor; keep the manifest versions intact
+for now (parallel, not replaced). Internally the manifest validators can be
+re-expressed in terms of the IR ones via a thin local adapter to avoid
+duplicating logic, but the **public** new surface is IR-typed.
+
+### Step 2 — Delete the buildgen IR→manifest shim
+
+- Switch `buildgen/build.go:37,43` to call the new `ValidateProgram` /
+ `ValidateBackendBindingPolicyIR`.
+- Delete `buildModelFromIR` and its helpers in `internal/buildgen/ir.go`
+ (`buildPageFromIR`, `buildComponentFromIR`, `buildLayoutFromIR`,
+ `buildBackendBindingFromIR`, and the ~12 sub-helpers).
+- Migrate the remaining buildgen render helpers that still take
+ `manifest.Page/Component/Layout` to take the `gwdkir` equivalents. These are
+ mechanical field-rename changes since the IR types mirror the manifest ones.
+ This is the bulk of buildgen's 731 refs and can be done file-by-file
+ (`css.go`, `components.go`, `render.go`, `routes.go`, …), each compiling and
+ testing green on its own.
+
+**Exit criterion for Step 2:** `internal/buildgen` no longer imports
+`internal/manifest`.
+
+### Step 3 — Make backend binding + endpoint discovery IR-native
+
+- `BindBackendHandlers(app manifest.Manifest) manifest.Manifest`
+ (`backend_bindings.go:38`) currently mutates manifest. Move binding discovery
+ to populate `gwdkir.Endpoint.Binding` directly (the IR already has the field;
+ `attachBackendBindings` in `gwdkanalysis/ir_bindings.go:10` already copies it
+ across). Provide `BindBackendHandlersIR(ir *gwdkir.Program)`.
+- `DiscoverGoEndpointComments(app) (app, error)` (`go_endpoints.go:18`) — same
+ treatment: discover into IR endpoints.
+- Update `BuildRouteMetadata` (`route_bindings.go:105`) to take IR (it already
+ builds IR internally and discards the manifest afterward, so this mostly
+ deletes the manifest plumbing).
+- Update the `cmd/gowdk/build.go:89-116` orchestration to call the IR-native
+ validate/bind path. After this, the CLI build path no longer needs manifest
+ between BuildIR and codegen.
+
+**Exit criterion for Step 3:** `internal/compiler` no longer imports
+`internal/manifest` (public JSON aside — confirm `json.go` is the only
+remaining manifest consumer module-wide besides the parser seam).
+
+### Step 4 — appgen + lang/lsp cleanup
+
+- `appgen` is already IR-first at its boundary (`Options.IR`,
+ `actionEndpointsFromIR`, `apiEndpointsFromIR`). Convert the residual
+ manifest-typed helpers (`routes.go`, `types.go`, `source_*.go`,
+ `validate_*.go`) to IR types — mechanical, mirrors Step 2.
+- `lang` (`sitemap.go`, `accessibility.go`, `tools.go`) and `lsp`
+ (`completion_hover.go`, `components.go`, `diagnostics.go`): derive reports and
+ symbols from the IR snapshot. LSP open-document source spans stay as-is
+ (architecture doc keeps those); only project-wide symbol/report derivation
+ moves to IR.
+
+### Step 5 — Collapse the AST→manifest→IR path to AST→IR (the backbone)
+
+Only after Steps 1–4 leave manifest used by just the parser seam + public JSON:
+
+- Add `gwdkanalysis.BuildIRFromAST(config, files []gwdkast.File) gwdkir.Program`
+ that lowers typed AST directly to IR (reusing the existing `lowerIRPage` /
+ `lowerIRComponent` logic, but sourcing from AST rather than manifest records).
+- Repoint the parser/`lang.ParseBuildFiles` path to feed AST into the new
+ builder.
+- Keep `BuildIR(manifest.Manifest)` and the AST→manifest lowering in
+ `manifest_lowering.go` ALIVE but used **only** to produce public manifest
+ JSON, isolated behind `internal/manifest/json.go`. This satisfies architecture
+ doc step 5 ("keep public manifest JSON compatibility until a release plan
+ explicitly deprecates it").
+
+## Files Expected To Change
+
+- New: `internal/compiler/validate_program.go`, `.../backend_bindings_ir.go`,
+ `internal/gwdkanalysis/ir_from_ast.go`, plus the Step 0 golden test.
+- Heavy edits: `internal/buildgen/*` (delete `ir.go` shim; field renames across
+ render helpers), `internal/compiler/route_bindings.go`,
+ `backend_bindings.go`, `go_endpoints.go`, `validate*.go`,
+ `internal/appgen/*`, `cmd/gowdk/build.go`, `route_report.go`, `dev_loop.go`.
+- Light edits: `internal/lang/*`, `internal/lsp/*`.
+- Untouched on purpose: `internal/manifest/json.go` (public wire format),
+ parser's AST output, `gwdkir/ir.go` types (add fields only if a gap is found).
+
+## Data And API Impact
+
+- No change to public manifest JSON, generated HTML, generated Go, route
+ manifests, or build reports. The golden snapshot in Step 0 enforces this.
+- Internal-only API surface change: new IR-typed validation/binding entrypoints;
+ removal of the buildgen IR→manifest shim. `internal/` packages have no
+ external API stability guarantee.
+
+## Tests
+
+- Unit: IR-twin validators must reproduce the exact `ValidationError` set the
+ manifest validators produce. Add table tests that run both on the same fixture
+ IR/manifest and assert identical error slices during the parallel-existence
+ window (Steps 1–4).
+- Integration: the Step 0 build-orchestration golden test (IR snapshot +
+ output file list) gates every step.
+- End-to-end: `scripts/test-go-modules.sh` (root + nested adapter modules).
+- Manual: `gowdk build` on `examples/` that have buildable targets; diff
+ `dist/` output byte-for-byte against pre-migration output.
+
+## Verification Commands
+
+```sh
+# Per-package, after each step:
+go build ./... && go vet ./internal/buildgen ./internal/compiler ./internal/appgen
+gofmt -l internal cmd
+
+# Confirm a package has shed its manifest dependency (run after its step):
+grep -rl 'gowdk/internal/manifest' internal/buildgen --include='*.go' | grep -v _test.go # expect empty after Step 2
+grep -rl 'gowdk/internal/manifest' internal/compiler --include='*.go' | grep -v _test.go # expect empty after Step 3
+
+# Full gate:
+scripts/test-go-modules.sh
+```
+
+## Rollback Plan
+
+- Each step is a separate PR that compiles and passes tests on its own. Rolling
+ back is reverting that PR; later steps depend on earlier ones but no step
+ leaves the tree in a non-building state.
+- The parallel-existence strategy (Steps 1, 3 add IR entrypoints beside the
+ manifest ones rather than replacing in place) means a regression in an IR
+ validator can be reverted to the manifest path without unwinding consumer
+ edits.
+
+## Risks
+
+- **Validator drift:** the IR twins must reproduce manifest validation exactly.
+ Mitigated by the dual-run table tests during the parallel window (Tests §).
+- **Lossy shim removal:** `buildModelFromIR` reconstructs manifest from IR; if
+ any buildgen render helper depended on a manifest field NOT present in IR,
+ Step 2 surfaces it as a compile error → add the field to `gwdkir` first
+ (architecture doc rule: "add fields to IR first"). Audit for this in Step 2.
+- **Span/value type sharing:** `gwdkir`, `gwdkast`, and `manifest` share span
+ and value types (80 / 35 refs). Decide ownership early — likely move the
+ shared value types into a neutral package or `gwdkir` to avoid a residual
+ manifest import just for `SourceSpan`.
+- **Scope creep into public JSON:** explicitly fenced off. If a reviewer pushes
+ to also migrate `json.go`, that is a separate release-gated decision.
+
+## Suggested PR Sequence
+
+1. Step 0 golden test (safety net).
+2. Step 1 IR-native compiler validation (parallel, additive).
+3. Step 2 delete buildgen shim + buildgen field migration. **Highest payoff.**
+4. Step 3 IR-native backend binding/discovery + CLI orchestration.
+5. Step 4 appgen + lang/lsp.
+6. Step 5 AST→IR backbone; isolate manifest to public JSON only.
diff --git a/docs/engineering/architecture.md b/docs/engineering/architecture.md
index 86f13e4..92f8171 100644
--- a/docs/engineering/architecture.md
+++ b/docs/engineering/architecture.md
@@ -107,6 +107,17 @@ code still carries manifest compatibility records while migration continues:
compatibility records for older compiler entrypoints.
- `internal/manifest` remains the public manifest/site-map compatibility model.
+Shared leaf value types (source spans, route params, inline scripts, backend
+binding/signature enums) now live in the neutral `internal/source` package.
+`internal/manifest` re-exports them as type aliases for backward compatibility.
+Because of this, `internal/gwdkir`, `internal/gwdkast`, and `internal/contractscan`
+no longer depend on `internal/manifest` at all — the IR is a manifest-independent
+handoff. `internal/buildgen` render helpers consume IR page/component/layout
+models directly (validating through `compiler.ValidateProgram`), and
+`internal/appgen` references shared leaf types from `internal/source`. The
+remaining manifest coupling is concentrated in `internal/compiler` (which
+validates the manifest model) and the public `manifest.Manifest` entrypoints.
+
New generated-output work should consume `internal/gwdkir.Program` or add fields
there first. Removing the remaining compatibility records is planned after the
parser, public manifest JSON, and legacy compiler entrypoints stop depending on
@@ -119,8 +130,8 @@ Current manifest compatibility users:
| `internal/parser` | Produces `manifest.Page`, `manifest.Component`, and `manifest.Layout` records for existing CLI and compiler entrypoints. | Keep typed AST as the parser source of truth; remove direct manifest output after all callers lower through analyzer/IR. |
| `internal/gwdkanalysis` | Lowers typed AST into manifest records, then builds `gwdkir.Program` from those records. | Lower typed AST directly into IR and keep manifest output as a separate compatibility adapter. |
| `internal/compiler` | Validators, backend binding discovery, route conflict checks, and policy checks still consume `manifest.Manifest`. | Move validation and binding metadata to IR models; keep adapters only for public manifest and legacy entrypoints. |
-| `internal/buildgen` | Public `Build`/`BuildMemory` entrypoints accept `manifest.Manifest`; supported generation paths derive or consume IR but still convert IR back to manifest models for older render helpers. | Make render helpers consume IR-native page/component/layout models, then keep manifest entrypoints as thin adapters. |
-| `internal/appgen` | Public route helpers and some backend adapter types still use manifest page, endpoint, and backend-binding records. | Keep app route planning and backend adapter planning IR-first; replace manifest-only helper entrypoints with IR helpers before removing compatibility constructors. |
+| `internal/buildgen` | Render helpers now consume IR-native page/component/layout/blocks models directly. Public `Build`/`BuildMemory` entrypoints still accept `manifest.Manifest` for compatibility, and a small `gotypes` bridge plus the public `SSRArtifact.LoadBinding` output type keep manifest references at the package edges. | Keep manifest entrypoints as thin adapters; remove the `gotypes` bridge once `internal/gotypes` accepts IR types. |
+| `internal/appgen` | Shared leaf types come from `internal/source`. The `manifest.BackendBinding` struct currency (carrying Kind/PageID/Method/Route) and public `manifest.Manifest` route-helper entrypoints remain. | Keep app route planning and backend adapter planning IR-first; replace manifest-only helper entrypoints with IR helpers before removing compatibility constructors. |
| `internal/lang` and `cmd/gowdk` | CLI `check`, `manifest`, `sitemap`, `routes`, and build setup still pass manifest records between parse, validate, report, and generation steps. | Parse/analyze into IR once per command, then derive public JSON and reports from IR adapters. |
| `internal/lsp` | Open-document completions and hover build an IR snapshot before indexing page, component, layout, and endpoint symbols; workspace component definition lookup indexes IR components; diagnostics and open-buffer owner context still use language tooling and parser compatibility records. | Keep open-document source spans, but drive remaining project-wide symbols and diagnostics from analyzer/IR snapshots. |
| Tests | Many generator and validator tests construct manifest records directly. | Keep fixture-style manifest construction until the tested package exposes IR-native helpers; update tests alongside each migrated package. |
@@ -129,12 +140,19 @@ Migration order:
1. Keep `gwdkir.Program` as the only source of truth for new generated-output
fields.
-2. Move compiler validation and backend binding records to IR or IR-adjacent
- structs.
-3. Replace buildgen/appgen manifest render helpers with IR-native helpers.
-4. Convert CLI reports, sitemap output, and LSP project metadata to IR-derived
+2. (done) Extract shared leaf value types into `internal/source` so the IR and
+ manifest both reference them from a neutral home; `gwdkir` no longer depends
+ on `manifest`.
+3. (done) Replace buildgen render helpers with IR-native helpers and route
+ buildgen validation through `compiler.ValidateProgram`.
+4. Move compiler validation and backend binding records to IR or IR-adjacent
+ structs (compiler still validates the manifest model today). This is a
+ redesign — IR lowering is currently lossy for standalone-endpoint validation,
+ so the IR must be enriched and the flip guarded against diagnostic drift.
+ Tracked in issue #145.
+5. Convert CLI reports, sitemap output, and LSP project metadata to IR-derived
adapters.
-5. Keep public manifest JSON compatibility until a release plan explicitly
+6. Keep public manifest JSON compatibility until a release plan explicitly
deprecates or replaces it.
## Components
diff --git a/internal/appgen/adapter_ir.go b/internal/appgen/adapter_ir.go
index 97412c9..82d90c3 100644
--- a/internal/appgen/adapter_ir.go
+++ b/internal/appgen/adapter_ir.go
@@ -4,7 +4,7 @@ import (
"sort"
"github.com/cssbruno/gowdk/internal/gwdkir"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
type BackendAdapterIR struct {
@@ -46,7 +46,7 @@ type BackendHandlerCall struct {
Endpoint BackendEndpointRegistration
Alias string
Function string
- Signature manifest.BackendSignatureKind
+ Signature source.BackendSignatureKind
InputType string
}
@@ -59,7 +59,7 @@ type BackendResponse struct {
type BackendFallback struct {
Endpoint BackendEndpointRegistration
- Status manifest.BackendBindingStatus
+ Status source.BackendBindingStatus
Message string
}
@@ -72,7 +72,7 @@ type BackendContractExposure struct {
Result string
Roles []string
Guards []string
- InputFields []manifest.BackendInputField
+ InputFields []source.BackendInputField
Status gwdkir.ContractBindingStatus
Handler string
Register string
@@ -101,7 +101,7 @@ func backendAdapterIR(options Options) BackendAdapterIR {
Input: action.InputType,
Fields: append([]string(nil), action.InputFields...),
}
- if action.Binding.Status == manifest.BackendBindingBound && action.Binding.InputType != "" {
+ if action.Binding.Status == source.BackendBindingBound && action.Binding.InputType != "" {
decoder.Function = boundActionDecoderName(action)
decoder.Input = action.Binding.InputType
} else if action.InputType != "" {
@@ -109,7 +109,7 @@ func backendAdapterIR(options Options) BackendAdapterIR {
}
ir.Decoders = append(ir.Decoders, decoder)
}
- if action.Binding.Status == manifest.BackendBindingBound {
+ if action.Binding.Status == source.BackendBindingBound {
ir.Calls = append(ir.Calls, BackendHandlerCall{
Endpoint: endpoint,
Alias: action.BackendAlias,
@@ -118,7 +118,7 @@ func backendAdapterIR(options Options) BackendAdapterIR {
InputType: action.Binding.InputType,
})
}
- if action.Binding.Status != "" && action.Binding.Status != manifest.BackendBindingBound {
+ if action.Binding.Status != "" && action.Binding.Status != source.BackendBindingBound {
ir.Fallbacks = append(ir.Fallbacks, BackendFallback{
Endpoint: endpoint,
Status: action.Binding.Status,
@@ -142,7 +142,7 @@ func backendAdapterIR(options Options) BackendAdapterIR {
Name: api.APIName,
}
ir.Registrations = append(ir.Registrations, endpoint)
- if api.Binding.Status == manifest.BackendBindingBound {
+ if api.Binding.Status == source.BackendBindingBound {
ir.Calls = append(ir.Calls, BackendHandlerCall{
Endpoint: endpoint,
Alias: api.BackendAlias,
@@ -150,7 +150,7 @@ func backendAdapterIR(options Options) BackendAdapterIR {
Signature: api.Binding.Signature,
})
}
- if api.Binding.Status != "" && api.Binding.Status != manifest.BackendBindingBound {
+ if api.Binding.Status != "" && api.Binding.Status != source.BackendBindingBound {
ir.Fallbacks = append(ir.Fallbacks, BackendFallback{
Endpoint: endpoint,
Status: api.Binding.Status,
@@ -194,7 +194,7 @@ func backendAdapterIR(options Options) BackendAdapterIR {
Result: ref.Result,
Roles: append([]string(nil), ref.Roles...),
Guards: append([]string(nil), ref.Guards...),
- InputFields: append([]manifest.BackendInputField(nil), ref.InputFields...),
+ InputFields: append([]source.BackendInputField(nil), ref.InputFields...),
Status: ref.Status,
Handler: ref.Handler,
Register: ref.Register,
diff --git a/internal/appgen/auto_routes.go b/internal/appgen/auto_routes.go
index c7ab051..1b32682 100644
--- a/internal/appgen/auto_routes.go
+++ b/internal/appgen/auto_routes.go
@@ -9,7 +9,7 @@ import (
"github.com/cssbruno/gowdk/internal/buildgen"
"github.com/cssbruno/gowdk/internal/gwdkir"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func resolveOptions(outputDir string, options Options) (Options, error) {
@@ -88,22 +88,22 @@ func optionsIR(options Options) (gwdkir.Program, error) {
func assignBackendAliases(options *Options) {
paths := map[string]string{}
for _, action := range options.Actions {
- if action.Binding.Status == manifest.BackendBindingBound && action.Binding.ImportPath != "" {
+ if action.Binding.Status == source.BackendBindingBound && action.Binding.ImportPath != "" {
paths[action.Binding.ImportPath] = action.Binding.PackageName
}
}
for _, api := range options.APIs {
- if api.Binding.Status == manifest.BackendBindingBound && api.Binding.ImportPath != "" {
+ if api.Binding.Status == source.BackendBindingBound && api.Binding.ImportPath != "" {
paths[api.Binding.ImportPath] = api.Binding.PackageName
}
}
for _, fragment := range options.Fragments {
- if fragment.Binding.Status == manifest.BackendBindingBound && fragment.Binding.ImportPath != "" {
+ if fragment.Binding.Status == source.BackendBindingBound && fragment.Binding.ImportPath != "" {
paths[fragment.Binding.ImportPath] = fragment.Binding.PackageName
}
}
for _, route := range options.SSR {
- if route.LoadBinding.Status == manifest.BackendBindingBound && route.LoadBinding.ImportPath != "" {
+ if route.LoadBinding.Status == source.BackendBindingBound && route.LoadBinding.ImportPath != "" {
paths[route.LoadBinding.ImportPath] = route.LoadBinding.PackageName
}
}
@@ -171,7 +171,7 @@ func ssrRoutes(artifacts []buildgen.SSRArtifact) []SSRRoute {
Cache: artifact.Cache,
ErrorPage: artifact.ErrorPage,
DynamicParams: append([]string(nil), artifact.DynamicParams...),
- RouteParams: append([]manifest.RouteParam(nil), artifact.RouteParams...),
+ RouteParams: append([]source.RouteParam(nil), artifact.RouteParams...),
Guards: append([]string(nil), artifact.Guards...),
HasLoad: artifact.HasLoad,
LoadBinding: artifact.LoadBinding,
diff --git a/internal/appgen/ir.go b/internal/appgen/ir.go
index ebd66cf..fcdc7de 100644
--- a/internal/appgen/ir.go
+++ b/internal/appgen/ir.go
@@ -6,6 +6,7 @@ import (
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/internal/view"
)
@@ -149,8 +150,8 @@ func fragmentEndpointsFromIR(ir gwdkir.Program) ([]FragmentEndpoint, error) {
return endpoints, nil
}
-func renderFragmentHTML(source string, packageName string, uses map[string]string, components map[string]view.Component) (string, error) {
- return view.RenderWithOptions(source, componentRegistryForFragment(packageName, uses, components), nil, view.Options{
+func renderFragmentHTML(body string, packageName string, uses map[string]string, components map[string]view.Component) (string, error) {
+ return view.RenderWithOptions(body, componentRegistryForFragment(packageName, uses, components), nil, view.Options{
Package: packageName,
Uses: uses,
})
@@ -285,7 +286,7 @@ func irBindingsByEndpoint(endpoints []gwdkir.Endpoint) map[string]manifest.Backe
Signature: endpoint.Binding.Signature,
InputType: endpoint.Binding.InputType,
InputPointer: endpoint.Binding.InputPointer,
- InputFields: append([]manifest.BackendInputField(nil), endpoint.Binding.InputFields...),
+ InputFields: append([]source.BackendInputField(nil), endpoint.Binding.InputFields...),
Status: endpoint.Binding.Status,
Message: endpoint.Binding.Message,
}
@@ -312,7 +313,7 @@ func actionFragmentsFromIR(action gwdkir.Action) ([]ActionFragment, error) {
return fragments, nil
}
-func bindingInputFieldNames(fields []manifest.BackendInputField) []string {
+func bindingInputFieldNames(fields []source.BackendInputField) []string {
out := make([]string, 0, len(fields))
for _, field := range fields {
out = append(out, field.FormName)
diff --git a/internal/appgen/scripts.go b/internal/appgen/scripts.go
index 2e8ab6f..b0ead74 100644
--- a/internal/appgen/scripts.go
+++ b/internal/appgen/scripts.go
@@ -10,6 +10,7 @@ import (
"github.com/cssbruno/gowdk/internal/goblockgen"
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
type inlineGoBlockGroup struct {
@@ -32,15 +33,15 @@ func writeInlineGoBlockFiles(appDir string, options Options) ([]string, error) {
}
var files []string
for packageName, group := range groups {
- source, err := goblockgen.Source(packageName, group.imports, group.goBlocks)
+ generated, err := goblockgen.Source(packageName, group.imports, group.goBlocks)
if err != nil {
return nil, fmt.Errorf("generate inline go block package %s: %w", packageName, err)
}
- if len(source) == 0 {
+ if len(generated) == 0 {
continue
}
relPath := goblockgen.GeneratedRelPath(packageName)
- if err := writeFileIfChanged(filepath.Join(appDir, relPath), source); err != nil {
+ if err := writeFileIfChanged(filepath.Join(appDir, relPath), generated); err != nil {
return nil, err
}
files = append(files, relPath)
@@ -162,15 +163,15 @@ func writeAddonGoBlockFiles(appDir string, options Options) ([]string, error) {
if err != nil {
return nil, err
}
- source := []byte(file.Source)
+ contents := []byte(file.Source)
if strings.HasSuffix(relPath, ".go") {
- formatted, err := format.Source(source)
+ formatted, err := format.Source(contents)
if err != nil {
return nil, fmt.Errorf("format addon go block file %s: %w", relPath, err)
}
- source = formatted
+ contents = formatted
}
- if err := writeFileIfChanged(filepath.Join(appDir, relPath), source); err != nil {
+ if err := writeFileIfChanged(filepath.Join(appDir, relPath), contents); err != nil {
return nil, err
}
files = append(files, relPath)
@@ -240,13 +241,13 @@ func addonGoBlockTargets(ir gwdkir.Program, config gowdk.Config) []addonGoBlockT
return targets
}
-func gowdkGoBlockTarget(ownerKind string, ownerID string, packageName string, source string, target string, body string, span manifest.SourceSpan) gowdk.GoBlockTarget {
+func gowdkGoBlockTarget(ownerKind string, ownerID string, packageName string, sourcePath string, target string, body string, span source.SourceSpan) gowdk.GoBlockTarget {
return gowdk.GoBlockTarget{
Target: target,
OwnerKind: ownerKind,
OwnerID: ownerID,
OwnerPackage: packageName,
- SourcePath: source,
+ SourcePath: sourcePath,
Body: body,
Span: gowdk.SourceSpan{
Start: gowdk.SourcePosition{Line: span.Start.Line, Column: span.Start.Column},
diff --git a/internal/appgen/source_actions.go b/internal/appgen/source_actions.go
index 9f28f62..dfceebb 100644
--- a/internal/appgen/source_actions.go
+++ b/internal/appgen/source_actions.go
@@ -11,6 +11,7 @@ import (
"strings"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func actionHandlerSource(actions []ActionEndpoint, csrf bool) string {
@@ -59,12 +60,12 @@ func actionsUseLengthValidation(actions []ActionEndpoint) bool {
}
func actionsUseActionValidation(action ActionEndpoint) bool {
- return action.Binding.Status != manifest.BackendBindingMissing && action.Binding.Status != manifest.BackendBindingUnsupportedSignature && action.ValidatesInput
+ return action.Binding.Status != source.BackendBindingMissing && action.Binding.Status != source.BackendBindingUnsupportedSignature && action.ValidatesInput
}
func actionsUseForm(actions []ActionEndpoint) bool {
for _, action := range actions {
- if action.Binding.Status != manifest.BackendBindingMissing && action.Binding.Status != manifest.BackendBindingUnsupportedSignature && actionNeedsValues(action) {
+ if action.Binding.Status != source.BackendBindingMissing && action.Binding.Status != source.BackendBindingUnsupportedSignature && actionNeedsValues(action) {
return true
}
}
@@ -73,7 +74,7 @@ func actionsUseForm(actions []ActionEndpoint) bool {
func actionsParseForm(actions []ActionEndpoint) bool {
for _, action := range actions {
- if action.Binding.Status != manifest.BackendBindingMissing && action.Binding.Status != manifest.BackendBindingUnsupportedSignature {
+ if action.Binding.Status != source.BackendBindingMissing && action.Binding.Status != source.BackendBindingUnsupportedSignature {
return true
}
}
@@ -92,13 +93,13 @@ func sortedActionEndpoints(actions []ActionEndpoint) []ActionEndpoint {
}
func actionNeedsValues(action ActionEndpoint) bool {
- if action.Binding.Status != manifest.BackendBindingBound {
+ if action.Binding.Status != source.BackendBindingBound {
return true
}
if action.ValidatesInput {
return true
}
- return action.Binding.Signature != manifest.BackendSignatureAction0
+ return action.Binding.Signature != source.BackendSignatureAction0
}
func actionFuncDecl(actions []ActionEndpoint, csrf bool, rateLimit bool) *ast.FuncDecl {
@@ -143,7 +144,7 @@ func actionCaseStmts(action ActionEndpoint, csrf bool, rateLimit bool) []ast.Stm
}
stmts = append(stmts, rateLimitStmts(rateLimit)...)
stmts = append(stmts, guardStmts(action.Guards)...)
- if action.Binding.Status != "" && action.Binding.Status != manifest.BackendBindingBound {
+ if action.Binding.Status != "" && action.Binding.Status != source.BackendBindingBound {
stmts = append(stmts, backendNotImplementedStmts(action.Binding, "action")...)
stmts = append(stmts, returnBool(true))
return stmts
@@ -153,7 +154,7 @@ func actionCaseStmts(action ActionEndpoint, csrf bool, rateLimit bool) []ast.Stm
stmts = append(stmts, define([]ast.Expr{id("values")}, call(sel("gowdkform", "FromURLValues"), selExpr(id("request"), "PostForm"))))
}
stmts = append(stmts, actionInputDecodeStmts(action)...)
- if action.Binding.Status == manifest.BackendBindingBound {
+ if action.Binding.Status == source.BackendBindingBound {
stmts = append(stmts, boundActionResultStmts(action)...)
} else {
stmts = append(stmts, actionPartialBranchStmts(action)...)
@@ -199,7 +200,7 @@ func actionParseFormStmts(csrf bool) []ast.Stmt {
}
func actionInputDecodeStmts(action ActionEndpoint) []ast.Stmt {
- if action.Binding.Status == manifest.BackendBindingBound {
+ if action.Binding.Status == source.BackendBindingBound {
return boundActionInputDecodeStmts(action)
}
if action.InputType == "" {
@@ -219,18 +220,18 @@ func actionInputDecodeStmts(action ActionEndpoint) []ast.Stmt {
func boundActionInputDecodeStmts(action ActionEndpoint) []ast.Stmt {
switch action.Binding.Signature {
- case manifest.BackendSignatureAction0:
+ case source.BackendSignatureAction0:
if action.ValidatesInput {
return actionRequiredValidationStmts(action)
}
return nil
- case manifest.BackendSignatureActionValues:
+ case source.BackendSignatureActionValues:
stmts := expectedValuesStmts(action)
if action.ValidatesInput {
stmts = append(stmts, actionRequiredValidationStmts(action)...)
}
return stmts
- case manifest.BackendSignatureActionForm, manifest.BackendSignatureActionFormPtr:
+ case source.BackendSignatureActionForm, source.BackendSignatureActionFormPtr:
stmts := expectedValuesStmts(action)
stmts = append(stmts,
define([]ast.Expr{id("input"), id("err")}, call(sel(boundActionDecoderName(action)), id("values"))),
@@ -364,10 +365,10 @@ func actionValidationMessage(custom string, fallback string) string {
func boundActionResultStmts(action ActionEndpoint) []ast.Stmt {
args := []ast.Expr{id("ctx")}
switch action.Binding.Signature {
- case manifest.BackendSignatureAction0:
- case manifest.BackendSignatureActionForm:
+ case source.BackendSignatureAction0:
+ case source.BackendSignatureActionForm:
args = append(args, id("input"))
- case manifest.BackendSignatureActionFormPtr:
+ case source.BackendSignatureActionFormPtr:
args = append(args, &ast.UnaryExpr{Op: token.AND, X: id("input")})
default:
args = append(args, id("values"))
@@ -550,7 +551,7 @@ func actionDecoderDecls(actions []ActionEndpoint) []ast.Decl {
})
}
for _, action := range actions {
- if action.Binding.Status == manifest.BackendBindingBound || action.Binding.Status == manifest.BackendBindingMissing || action.Binding.Status == manifest.BackendBindingUnsupportedSignature || action.InputType == "" {
+ if action.Binding.Status == source.BackendBindingBound || action.Binding.Status == source.BackendBindingMissing || action.Binding.Status == source.BackendBindingUnsupportedSignature || action.InputType == "" {
continue
}
decls = append(decls, valuesActionDecoderDecl(action))
@@ -591,7 +592,7 @@ func uniqueInputTypes(actions []ActionEndpoint) []string {
seen := map[string]bool{}
var types []string
for _, action := range actions {
- if action.Binding.Status == manifest.BackendBindingBound || action.Binding.Status == manifest.BackendBindingMissing || action.Binding.Status == manifest.BackendBindingUnsupportedSignature || action.InputType == "" || seen[action.InputType] {
+ if action.Binding.Status == source.BackendBindingBound || action.Binding.Status == source.BackendBindingMissing || action.Binding.Status == source.BackendBindingUnsupportedSignature || action.InputType == "" || seen[action.InputType] {
continue
}
seen[action.InputType] = true
@@ -602,10 +603,10 @@ func uniqueInputTypes(actions []ActionEndpoint) []string {
}
func actionUsesBoundInputDecoder(action ActionEndpoint) bool {
- if action.Binding.Status != manifest.BackendBindingBound {
+ if action.Binding.Status != source.BackendBindingBound {
return false
}
- return action.Binding.Signature == manifest.BackendSignatureActionForm || action.Binding.Signature == manifest.BackendSignatureActionFormPtr
+ return action.Binding.Signature == source.BackendSignatureActionForm || action.Binding.Signature == source.BackendSignatureActionFormPtr
}
func boundActionDecoderDecl(action ActionEndpoint) *ast.FuncDecl {
@@ -622,7 +623,7 @@ func boundActionDecoderDecl(action ActionEndpoint) *ast.FuncDecl {
}, []*ast.Field{{Type: inputType}, {Type: id("error")}}, stmts)
}
-func boundActionFieldDecodeStmts(index int, field manifest.BackendInputField) []ast.Stmt {
+func boundActionFieldDecodeStmts(index int, field source.BackendInputField) []ast.Stmt {
value := id(fmtFieldValueName(index))
switch field.Type {
case "string":
@@ -646,7 +647,7 @@ func boundActionFieldDecodeStmts(index int, field manifest.BackendInputField) []
}
}
-func boundActionScalarFieldDecodeStmts(value *ast.Ident, field manifest.BackendInputField, decode ast.Expr, assignment ast.Expr) []ast.Stmt {
+func boundActionScalarFieldDecodeStmts(value *ast.Ident, field source.BackendInputField, decode ast.Expr, assignment ast.Expr) []ast.Stmt {
return []ast.Stmt{
define([]ast.Expr{value, id("ok"), id("err")}, decode),
&ast.IfStmt{
diff --git a/internal/appgen/source_api.go b/internal/appgen/source_api.go
index d6578f2..fbe2d33 100644
--- a/internal/appgen/source_api.go
+++ b/internal/appgen/source_api.go
@@ -4,7 +4,7 @@ import (
"go/ast"
"go/token"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func apiHandlerSource(apis []APIEndpoint) string {
@@ -62,7 +62,7 @@ func apiCaseStmts(api APIEndpoint, rateLimit bool) []ast.Stmt {
}
stmts = append(stmts, rateLimitStmts(rateLimit)...)
stmts = append(stmts, guardStmts(api.Guards)...)
- if api.Binding.Status != manifest.BackendBindingBound {
+ if api.Binding.Status != source.BackendBindingBound {
stmts = append(stmts, backendNotImplementedStmts(api.Binding, "API")...)
stmts = append(stmts, returnBool(true))
return stmts
diff --git a/internal/appgen/source_contracts.go b/internal/appgen/source_contracts.go
index 05bba67..40a804d 100644
--- a/internal/appgen/source_contracts.go
+++ b/internal/appgen/source_contracts.go
@@ -7,7 +7,7 @@ import (
"strings"
"github.com/cssbruno/gowdk/internal/gwdkir"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func contractHandlerDecls(exposures []BackendContractExposure, csrf bool, rateLimit bool) []ast.Decl {
@@ -187,7 +187,7 @@ func contractDecoderDecl(exposure BackendContractExposure) *ast.FuncDecl {
}, []*ast.Field{{Type: inputType}, {Type: id("error")}}, stmts)
}
-func contractFormSchemaExpr(fields []manifest.BackendInputField) ast.Expr {
+func contractFormSchemaExpr(fields []source.BackendInputField) ast.Expr {
names := make([]string, 0, len(fields))
for _, field := range fields {
names = append(names, field.FormName)
diff --git a/internal/appgen/source_fragments.go b/internal/appgen/source_fragments.go
index e592854..118fe62 100644
--- a/internal/appgen/source_fragments.go
+++ b/internal/appgen/source_fragments.go
@@ -5,7 +5,7 @@ import (
"go/token"
"sort"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func fragmentFuncDecl(fragments []FragmentEndpoint, rateLimit bool) *ast.FuncDecl {
@@ -50,12 +50,12 @@ func fragmentCaseStmts(fragment FragmentEndpoint, rateLimit bool) []ast.Stmt {
stmts := endpointContextStmts("fragment", fragment.PageID, fragment.FragmentName, fragment.Method, fragment.Route, "")
stmts = append(stmts, rateLimitStmts(rateLimit)...)
stmts = append(stmts, guardStmts(fragment.Guards)...)
- if fragment.Binding.Status == manifest.BackendBindingUnsupportedSignature {
+ if fragment.Binding.Status == source.BackendBindingUnsupportedSignature {
stmts = append(stmts, backendNotImplementedStmts(fragment.Binding, "fragment")...)
stmts = append(stmts, returnBool(true))
return stmts
}
- if fragment.Binding.Status == manifest.BackendBindingBound {
+ if fragment.Binding.Status == source.BackendBindingBound {
stmts = append(stmts,
define([]ast.Expr{id("result"), id("err")}, call(sel(fragment.BackendAlias, fragment.Binding.FunctionName), id("ctx"))),
&ast.IfStmt{
diff --git a/internal/appgen/source_ssr.go b/internal/appgen/source_ssr.go
index 93cc745..a5d1485 100644
--- a/internal/appgen/source_ssr.go
+++ b/internal/appgen/source_ssr.go
@@ -5,7 +5,7 @@ import (
"go/token"
"sort"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func ssrHandlerSource(routes []SSRRoute) string {
@@ -106,7 +106,7 @@ func ssrLoadStmts(route SSRRoute) []ast.Stmt {
if !route.HasLoad {
return nil
}
- if route.LoadBinding.Status != manifest.BackendBindingBound {
+ if route.LoadBinding.Status != source.BackendBindingBound {
stmts := backendNotImplementedStmts(route.LoadBinding, "SSR load")
stmts = append(stmts, returnBool(true))
return stmts
@@ -116,7 +116,7 @@ func ssrLoadStmts(route SSRRoute) []ast.Stmt {
}
loadCall := call(sel(route.LoadBackendAlias, route.LoadBinding.FunctionName), id("loadContext"))
switch route.LoadBinding.Signature {
- case manifest.BackendSignatureLoadError:
+ case source.BackendSignatureLoadError:
stmts = append(stmts,
define([]ast.Expr{id("loadData"), id("err")}, loadCall),
&ast.IfStmt{
@@ -220,7 +220,7 @@ func ssrRouteContextStmts(route SSRRoute, includeParams bool) []ast.Stmt {
return stmts
}
-func typedRouteParamStmts(params []manifest.RouteParam) []ast.Stmt {
+func typedRouteParamStmts(params []source.RouteParam) []ast.Stmt {
if len(params) == 0 {
return nil
}
@@ -301,7 +301,7 @@ func routeParamDecodeFunc(paramType string) string {
}
}
-func routeParamMetadataExpr(params []manifest.RouteParam) ast.Expr {
+func routeParamMetadataExpr(params []source.RouteParam) ast.Expr {
elts := make([]ast.Expr, 0, len(params))
for _, param := range params {
paramType := param.Type
diff --git a/internal/appgen/types.go b/internal/appgen/types.go
index 9a56eec..f222cc2 100644
--- a/internal/appgen/types.go
+++ b/internal/appgen/types.go
@@ -4,6 +4,7 @@ import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
// Result describes generated app artifacts.
@@ -102,7 +103,7 @@ type SSRRoute struct {
Cache string
ErrorPage string
DynamicParams []string
- RouteParams []manifest.RouteParam
+ RouteParams []source.RouteParam
Guards []string
HasLoad bool
LoadBinding manifest.BackendBinding
diff --git a/internal/buildgen/build.go b/internal/buildgen/build.go
index aab80f6..b1be725 100644
--- a/internal/buildgen/build.go
+++ b/internal/buildgen/build.go
@@ -14,15 +14,15 @@ import (
)
func Build(config gowdk.Config, app manifest.Manifest, outputDir string) (Result, error) {
- return buildFromIR(config, app, gwdkanalysis.BuildIR(config, app), outputDir)
+ return buildFromIR(config, gwdkanalysis.BuildIR(config, app), app.BackendBindings, outputDir)
}
// BuildFromIR writes SPA build artifacts from normalized compiler IR.
func BuildFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string) (Result, error) {
- return buildFromIR(config, buildModelFromIR(ir), ir, outputDir)
+ return buildFromIR(config, ir, compiler.BackendBindingsFromIR(ir), outputDir)
}
-func buildFromIR(config gowdk.Config, app manifest.Manifest, ir gwdkir.Program, outputDir string) (Result, error) {
+func buildFromIR(config gowdk.Config, ir gwdkir.Program, backendBindings []manifest.BackendBinding, outputDir string) (Result, error) {
reporter := newBuildReporter("build", outputDir)
reporter.info("start", "build_started", "SPA build started", BuildEvent{
Data: map[string]string{
@@ -34,13 +34,13 @@ func buildFromIR(config gowdk.Config, app manifest.Manifest, ir gwdkir.Program,
if strings.TrimSpace(outputDir) == "" {
return Result{}, reporter.fail("validate", fmt.Errorf("build output directory is required"))
}
- if err := compiler.ValidateManifest(config, app); err != nil {
+ if err := compiler.ValidateProgram(config, ir); err != nil {
return Result{}, reporter.fail("validate", err)
}
reporter.info("validate", "manifest_valid", "manifest validation completed", BuildEvent{})
- reportBackendBindings(reporter, app.BackendBindings)
+ reportBackendBindings(reporter, backendBindings)
reportContractReferences(reporter, ir.ContractRefs)
- if err := compiler.ValidateBackendBindingPolicy(config, app); err != nil {
+ if err := compiler.ValidateBackendBindingPolicyIR(config, ir); err != nil {
return Result{}, reporter.fail("bind", err)
}
@@ -119,16 +119,16 @@ func buildFromIR(config gowdk.Config, app manifest.Manifest, ir gwdkir.Program,
}
func BuildMemory(config gowdk.Config, app manifest.Manifest, outputDir string) (MemoryResult, error) {
- return buildMemoryFromIR(config, app, gwdkanalysis.BuildIR(config, app), outputDir)
+ return buildMemoryFromIR(config, gwdkanalysis.BuildIR(config, app), app.BackendBindings, outputDir)
}
// BuildMemoryFromIR plans SPA build artifacts from normalized compiler IR
// without writing them to disk.
func BuildMemoryFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string) (MemoryResult, error) {
- return buildMemoryFromIR(config, buildModelFromIR(ir), ir, outputDir)
+ return buildMemoryFromIR(config, ir, compiler.BackendBindingsFromIR(ir), outputDir)
}
-func buildMemoryFromIR(config gowdk.Config, app manifest.Manifest, ir gwdkir.Program, outputDir string) (MemoryResult, error) {
+func buildMemoryFromIR(config gowdk.Config, ir gwdkir.Program, backendBindings []manifest.BackendBinding, outputDir string) (MemoryResult, error) {
reporter := newBuildReporter("memory", outputDir)
reporter.info("start", "build_started", "in-memory SPA build started", BuildEvent{
Data: map[string]string{
@@ -140,13 +140,13 @@ func buildMemoryFromIR(config gowdk.Config, app manifest.Manifest, ir gwdkir.Pro
if strings.TrimSpace(outputDir) == "" {
return MemoryResult{}, reporter.fail("validate", fmt.Errorf("build output directory is required"))
}
- if err := compiler.ValidateManifest(config, app); err != nil {
+ if err := compiler.ValidateProgram(config, ir); err != nil {
return MemoryResult{}, reporter.fail("validate", err)
}
reporter.info("validate", "manifest_valid", "manifest validation completed", BuildEvent{})
- reportBackendBindings(reporter, app.BackendBindings)
+ reportBackendBindings(reporter, backendBindings)
reportContractReferences(reporter, ir.ContractRefs)
- if err := compiler.ValidateBackendBindingPolicy(config, app); err != nil {
+ if err := compiler.ValidateBackendBindingPolicyIR(config, ir); err != nil {
return MemoryResult{}, reporter.fail("bind", err)
}
@@ -336,16 +336,16 @@ func reportContractReferences(reporter *buildReporter, refs []gwdkir.ContractRef
}
func BuildIncremental(config gowdk.Config, app manifest.Manifest, outputDir string, changedPageSources []string) (Result, error) {
- return buildIncrementalFromIR(config, app, gwdkanalysis.BuildIR(config, app), outputDir, changedPageSources)
+ return buildIncrementalFromIR(config, gwdkanalysis.BuildIR(config, app), outputDir, changedPageSources)
}
// BuildIncrementalFromIR incrementally renders changed SPA page outputs from
// normalized compiler IR.
func BuildIncrementalFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string, changedPageSources []string) (Result, error) {
- return buildIncrementalFromIR(config, buildModelFromIR(ir), ir, outputDir, changedPageSources)
+ return buildIncrementalFromIR(config, ir, outputDir, changedPageSources)
}
-func buildIncrementalFromIR(config gowdk.Config, app manifest.Manifest, ir gwdkir.Program, outputDir string, changedPageSources []string) (Result, error) {
+func buildIncrementalFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string, changedPageSources []string) (Result, error) {
reporter := newBuildReporter("incremental", outputDir)
reporter.info("start", "build_started", "incremental SPA build started", BuildEvent{
Data: map[string]string{
@@ -356,19 +356,19 @@ func buildIncrementalFromIR(config gowdk.Config, app manifest.Manifest, ir gwdki
if strings.TrimSpace(outputDir) == "" {
return Result{}, reporter.fail("validate", fmt.Errorf("build output directory is required"))
}
- if err := compiler.ValidateManifest(config, app); err != nil {
+ if err := compiler.ValidateProgram(config, ir); err != nil {
return Result{}, reporter.fail("validate", err)
}
reporter.info("validate", "manifest_valid", "manifest validation completed", BuildEvent{})
reportContractReferences(reporter, ir.ContractRefs)
- if err := compiler.ValidateBackendBindingPolicy(config, app); err != nil {
+ if err := compiler.ValidateBackendBindingPolicyIR(config, ir); err != nil {
return Result{}, reporter.fail("bind", err)
}
changedPages := sourcePathSet(changedPageSources)
- components, componentFailures := buildComponents(app.Components)
- layouts, layoutFailures := buildLayouts(app.Layouts)
- css, cssFailures := planCSS(config, app, outputDir)
+ components, componentFailures := buildComponents(ir.Components)
+ layouts, layoutFailures := buildLayouts(ir.Layouts)
+ css, cssFailures := planCSS(config, ir, outputDir)
scopedJS, scopedJSFailures := planScopedJSAssets(ir.Assets, outputDir)
baseStylesheets := append([]gowdk.Stylesheet{}, config.Build.Stylesheets...)
baseStylesheets = append(baseStylesheets, css.stylesheets...)
@@ -381,7 +381,7 @@ func buildIncrementalFromIR(config gowdk.Config, app manifest.Manifest, ir gwdki
if len(failures) > 0 {
return Result{}, reporter.fail("plan", errors.New(strings.Join(failures, "\n")))
}
- runtime, err := runtimeArtifacts(config, app, outputDir, layouts, components)
+ runtime, err := runtimeArtifacts(config, ir, outputDir, layouts, components)
if err != nil {
return Result{}, reporter.fail("plan", err)
}
@@ -394,7 +394,7 @@ func buildIncrementalFromIR(config gowdk.Config, app manifest.Manifest, ir gwdki
})
result := Result{
- Artifacts: make([]Artifact, 0, len(app.Pages)),
+ Artifacts: make([]Artifact, 0, len(ir.Pages)),
CSSArtifacts: make([]CSSArtifact, 0, len(css.assets)),
AssetArtifacts: make([]AssetArtifact, 0, 1),
}
@@ -422,7 +422,7 @@ func buildIncrementalFromIR(config gowdk.Config, app manifest.Manifest, ir gwdki
}
seenOutputPaths := map[string]string{}
- for _, page := range app.Pages {
+ for _, page := range ir.Pages {
if isRequestTimePage(config, page) {
continue
}
@@ -511,10 +511,9 @@ func plan(config gowdk.Config, app manifest.Manifest, outputDir string) (buildPl
}
func planFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string) (buildPlan, error) {
- app := buildModelFromIR(ir)
- components, componentFailures := buildComponents(app.Components)
- layouts, layoutFailures := buildLayouts(app.Layouts)
- css, cssFailures := planCSS(config, app, outputDir)
+ components, componentFailures := buildComponents(ir.Components)
+ layouts, layoutFailures := buildLayouts(ir.Layouts)
+ css, cssFailures := planCSS(config, ir, outputDir)
componentAssets, componentAssetFailures := planComponentFileAssets(ir.Assets, outputDir)
scopedJS, scopedJSFailures := planScopedJSAssets(ir.Assets, outputDir)
baseStylesheets := append([]gowdk.Stylesheet{}, config.Build.Stylesheets...)
@@ -527,7 +526,7 @@ func planFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string) (build
failures = append(failures, cssFailures...)
failures = append(failures, componentAssetFailures...)
failures = append(failures, scopedJSFailures...)
- for _, page := range app.Pages {
+ for _, page := range ir.Pages {
if isRequestTimePage(config, page) {
continue
}
@@ -555,7 +554,7 @@ func planFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string) (build
if len(failures) > 0 {
return buildPlan{}, errors.New(strings.Join(failures, "\n"))
}
- runtime, err := runtimeArtifacts(config, app, outputDir, layouts, components)
+ runtime, err := runtimeArtifacts(config, ir, outputDir, layouts, components)
if err != nil {
return buildPlan{}, err
}
diff --git a/internal/buildgen/build_data_runner.go b/internal/buildgen/build_data_runner.go
index 260d37f..c928a6e 100644
--- a/internal/buildgen/build_data_runner.go
+++ b/internal/buildgen/build_data_runner.go
@@ -15,10 +15,10 @@ import (
"strconv"
"strings"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
)
-func runBuildDataCallRef(ref buildCallRef, imports []manifest.Import, scripts []manifest.GoBlock, source string) (map[string]string, error) {
+func runBuildDataCallRef(ref buildCallRef, imports []gwdkir.Import, scripts []gwdkir.GoBlock, source string) (map[string]string, error) {
if ref.Alias == "" {
if script, ok := packageScriptWithFunction(scripts, ref.Function); ok {
return runInlineBuildDataCall(script, imports, source, ref.Function)
@@ -36,14 +36,14 @@ func runBuildDataCallRef(ref buildCallRef, imports []manifest.Import, scripts []
return runBuildDataCall(ref.Alias, item.Path, ref.Function)
}
-func packageScriptWithFunction(scripts []manifest.GoBlock, function string) (manifest.GoBlock, bool) {
+func packageScriptWithFunction(scripts []gwdkir.GoBlock, function string) (gwdkir.GoBlock, bool) {
for _, script := range scripts {
if !isStaticPackageGoBlockTarget(script.Target) {
continue
}
file, err := parseInlineGoBlockFile(script, "gowdkinline")
if err != nil {
- return manifest.GoBlock{}, false
+ return gwdkir.GoBlock{}, false
}
for _, declaration := range file.Decls {
functionDeclaration, ok := declaration.(*ast.FuncDecl)
@@ -52,7 +52,7 @@ func packageScriptWithFunction(scripts []manifest.GoBlock, function string) (man
}
}
}
- return manifest.GoBlock{}, false
+ return gwdkir.GoBlock{}, false
}
func isStaticPackageGoBlockTarget(target string) bool {
@@ -64,7 +64,7 @@ func isStaticPackageGoBlockTarget(target string) bool {
}
}
-func runInlineBuildDataCall(script manifest.GoBlock, imports []manifest.Import, source string, function string) (map[string]string, error) {
+func runInlineBuildDataCall(script gwdkir.GoBlock, imports []gwdkir.Import, source string, function string) (map[string]string, error) {
runnerSource, err := inlineBuildDataRunnerSource(script, imports, source, function)
if err != nil {
return nil, err
@@ -91,7 +91,7 @@ func runInlineBuildDataCall(script manifest.GoBlock, imports []manifest.Import,
return parseBuildFunctionOutput(output)
}
-func inlineBuildDataRunnerSource(script manifest.GoBlock, imports []manifest.Import, source string, function string) (string, error) {
+func inlineBuildDataRunnerSource(script gwdkir.GoBlock, imports []gwdkir.Import, source string, function string) (string, error) {
if !isLiteralName(function) {
return "", fmt.Errorf("invalid build function name %q", function)
}
@@ -121,7 +121,7 @@ func inlineBuildDataRunnerSource(script manifest.GoBlock, imports []manifest.Imp
return string(formatted), nil
}
-func parseInlineGoBlockFile(script manifest.GoBlock, packageName string) (*ast.File, error) {
+func parseInlineGoBlockFile(script gwdkir.GoBlock, packageName string) (*ast.File, error) {
source := "package " + packageName + "\n" + script.Body
file, err := parser.ParseFile(token.NewFileSet(), "inline-script.gwdk.go", source, parser.AllErrors)
if err != nil {
@@ -142,7 +142,7 @@ func packageNameForInlineScript(source string) string {
return "gowdkinline"
}
-func inlineBuildDataImportDecl(imports []manifest.Import, scriptFile *ast.File) ast.Decl {
+func inlineBuildDataImportDecl(imports []gwdkir.Import, scriptFile *ast.File) ast.Decl {
specs := []ast.Spec{
&ast.ImportSpec{Name: ast.NewIdent("gowdkjson"), Path: buildDataStringLit("encoding/json")},
&ast.ImportSpec{Name: ast.NewIdent("gowdkos"), Path: buildDataStringLit("os")},
@@ -296,13 +296,13 @@ func runBuildDataCall(alias, importPath, function string) (map[string]string, er
return parseBuildFunctionOutput(output)
}
-func findBuildImport(alias string, imports []manifest.Import) (manifest.Import, bool) {
+func findBuildImport(alias string, imports []gwdkir.Import) (gwdkir.Import, bool) {
for _, item := range imports {
if item.Alias == alias {
return item, true
}
}
- return manifest.Import{}, false
+ return gwdkir.Import{}, false
}
func buildDataRunnerSource(alias, importPath, function string) (string, error) {
diff --git a/internal/buildgen/component_imports.go b/internal/buildgen/component_imports.go
index b3e6ca5..c0a0ac1 100644
--- a/internal/buildgen/component_imports.go
+++ b/internal/buildgen/component_imports.go
@@ -3,7 +3,7 @@ package buildgen
import (
"sort"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/view"
)
@@ -16,7 +16,7 @@ func componentRegistryKey(packageName, componentName string) string {
return internalComponentKeyPrefix + packageName + "." + componentName
}
-func componentRegistryForPage(page manifest.Page, registry map[string]view.Component) map[string]view.Component {
+func componentRegistryForPage(page gwdkir.Page, registry map[string]view.Component) map[string]view.Component {
if page.Package == "" && len(page.Uses) == 0 {
return registry
}
@@ -68,7 +68,7 @@ func sortedViewComponents(registry map[string]view.Component) []view.Component {
return out
}
-func manifestComponentIdentity(component manifest.Component) string {
+func manifestComponentIdentity(component gwdkir.Component) string {
if component.Package == "" {
return component.Name
}
diff --git a/internal/buildgen/components.go b/internal/buildgen/components.go
index 3f3f97b..bac7ce4 100644
--- a/internal/buildgen/components.go
+++ b/internal/buildgen/components.go
@@ -9,11 +9,12 @@ import (
"github.com/cssbruno/gowdk/internal/clientlang"
"github.com/cssbruno/gowdk/internal/cssscope"
"github.com/cssbruno/gowdk/internal/gotypes"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/internal/view"
)
-func buildComponents(components []manifest.Component) (map[string]view.Component, []string) {
+func buildComponents(components []gwdkir.Component) (map[string]view.Component, []string) {
registry := map[string]view.Component{}
var failures []string
for _, component := range components {
@@ -96,7 +97,7 @@ func buildComponents(components []manifest.Component) (map[string]view.Component
return registry, failures
}
-func viewInlineScripts(scripts []manifest.InlineScript) []view.InlineScript {
+func viewInlineScripts(scripts []source.InlineScript) []view.InlineScript {
if len(scripts) == 0 {
return nil
}
@@ -107,14 +108,14 @@ func viewInlineScripts(scripts []manifest.InlineScript) []view.InlineScript {
return out
}
-func componentDefaultIsland(component manifest.Component) string {
+func componentDefaultIsland(component gwdkir.Component) string {
if strings.TrimSpace(component.WASM.Package) != "" {
return "wasm"
}
return ""
}
-func componentScopeIDs(component manifest.Component) []string {
+func componentScopeIDs(component gwdkir.Component) []string {
if len(component.CSS) == 0 && strings.TrimSpace(component.Blocks.StyleBody) == "" {
return nil
}
@@ -130,7 +131,7 @@ func componentScopeIDs(component manifest.Component) []string {
return scopeIDs
}
-func componentUses(uses []manifest.Use) map[string]string {
+func componentUses(uses []gwdkir.Use) map[string]string {
if len(uses) == 0 {
return nil
}
@@ -141,7 +142,7 @@ func componentUses(uses []manifest.Use) map[string]string {
return out
}
-func componentClientComputeds(component manifest.Component) ([]clientlang.Computed, []string) {
+func componentClientComputeds(component gwdkir.Component) ([]clientlang.Computed, []string) {
if !component.Blocks.Client && strings.TrimSpace(component.Blocks.ClientBody) == "" {
return nil, nil
}
@@ -156,7 +157,7 @@ func componentClientComputeds(component manifest.Component) ([]clientlang.Comput
return computeds, nil
}
-func componentClientRefs(component manifest.Component) (map[string]clientlang.Ref, []string) {
+func componentClientRefs(component gwdkir.Component) (map[string]clientlang.Ref, []string) {
if !component.Blocks.Client && strings.TrimSpace(component.Blocks.ClientBody) == "" {
return nil, nil
}
@@ -167,7 +168,7 @@ func componentClientRefs(component manifest.Component) (map[string]clientlang.Re
return program.RefMap(), nil
}
-func componentClientHandlers(component manifest.Component) (map[string]clientlang.Handler, string, error) {
+func componentClientHandlers(component gwdkir.Component) (map[string]clientlang.Handler, string, error) {
emits := componentEmits(component)
if !component.Blocks.Client && strings.TrimSpace(component.Blocks.ClientBody) == "" && len(emits) == 0 {
return nil, "", nil
@@ -213,7 +214,7 @@ func componentClientHandlers(component manifest.Component) (map[string]clientlan
return handlers, string(payload), nil
}
-func componentEmits(component manifest.Component) map[string]clientlang.Emit {
+func componentEmits(component gwdkir.Component) map[string]clientlang.Emit {
if len(component.Emits) == 0 {
return nil
}
@@ -230,9 +231,9 @@ func componentEmits(component manifest.Component) map[string]clientlang.Emit {
return out
}
-func componentPropNames(component manifest.Component) ([]string, []string) {
+func componentPropNames(component gwdkir.Component) ([]string, []string) {
if component.PropsType.Name != "" {
- resolved, err := gotypes.ResolveStruct(component.Imports, component.PropsType)
+ resolved, err := gotypes.ResolveStruct(manifestImports(component.Imports), manifestGoTypeRef(component.PropsType))
if err != nil {
return nil, []string{fmt.Sprintf("component %s props: %v", component.Name, err)}
}
@@ -256,15 +257,15 @@ func componentPropNames(component manifest.Component) ([]string, []string) {
return props, failures
}
-func componentInitialState(component manifest.Component) (map[string]string, map[string]clientlang.ValueType, string, error) {
+func componentInitialState(component gwdkir.Component) (map[string]string, map[string]clientlang.ValueType, string, error) {
if component.State.Type.Name == "" {
return nil, nil, "", nil
}
- resolved, err := gotypes.ResolveStruct(component.Imports, component.State.Type)
+ resolved, err := gotypes.ResolveStruct(manifestImports(component.Imports), manifestGoTypeRef(component.State.Type))
if err != nil {
return nil, nil, "", err
}
- rawJSON, err := gotypes.RunStateInitJSON(component.Imports, component.State)
+ rawJSON, err := gotypes.RunStateInitJSON(manifestImports(component.Imports), manifestStateContract(component.State))
if err != nil {
return nil, nil, "", err
}
@@ -313,8 +314,8 @@ func stateValueString(value any) (string, bool) {
}
}
-func buildLayouts(layouts []manifest.Layout) (map[string]manifest.Layout, []string) {
- registry := map[string]manifest.Layout{}
+func buildLayouts(layouts []gwdkir.Layout) (map[string]gwdkir.Layout, []string) {
+ registry := map[string]gwdkir.Layout{}
var failures []string
for _, layout := range layouts {
if layout.ID == "" {
diff --git a/internal/buildgen/css.go b/internal/buildgen/css.go
index 0972484..dacafec 100644
--- a/internal/buildgen/css.go
+++ b/internal/buildgen/css.go
@@ -10,7 +10,7 @@ import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/internal/cssscope"
"github.com/cssbruno/gowdk/internal/discover"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/view"
)
@@ -26,13 +26,13 @@ type cssInput struct {
contents []byte
}
-func planCSS(config gowdk.Config, app manifest.Manifest, outputDir string) (cssPlan, []string) {
+func planCSS(config gowdk.Config, ir gwdkir.Program, outputDir string) (cssPlan, []string) {
planned := cssPlan{pageStylesheets: map[string][]gowdk.Stylesheet{}}
var failures []string
seen := map[string]bool{}
- pageIDs := pageIDSet(app.Pages)
+ pageIDs := pageIDSet(ir.Pages)
context := gowdk.CSSContext{
- Sources: cssSources(app),
+ Sources: cssSources(ir),
OutputDir: outputDir,
Build: config.Build,
CSS: config.CSS,
@@ -80,20 +80,20 @@ func planCSS(config gowdk.Config, app manifest.Manifest, outputDir string) (cssP
inputs, inputFailures := discoverCSSInputs(config, outputDir)
failures = append(failures, inputFailures...)
if len(inputFailures) == 0 {
- pageCSS, pageStylesheets, pageFailures := planPageCSS(config, app.Pages, outputDir, inputs, seen)
+ pageCSS, pageStylesheets, pageFailures := planPageCSS(config, ir.Pages, outputDir, inputs, seen)
failures = append(failures, pageFailures...)
planned.assets = append(planned.assets, pageCSS...)
for pageID, stylesheets := range pageStylesheets {
planned.pageStylesheets[pageID] = append(planned.pageStylesheets[pageID], stylesheets...)
}
}
- layoutCSS, layoutStylesheets, layoutFailures := planLayoutStyleCSS(app.Pages, app.Layouts, outputDir, seen)
+ layoutCSS, layoutStylesheets, layoutFailures := planLayoutStyleCSS(ir.Pages, ir.Layouts, outputDir, seen)
failures = append(failures, layoutFailures...)
planned.assets = append(planned.assets, layoutCSS...)
for pageID, stylesheets := range layoutStylesheets {
planned.pageStylesheets[pageID] = append(planned.pageStylesheets[pageID], stylesheets...)
}
- componentCSS, componentStylesheets, componentFailures := planComponentCSS(app.Components, outputDir, seen)
+ componentCSS, componentStylesheets, componentFailures := planComponentCSS(ir.Components, outputDir, seen)
failures = append(failures, componentFailures...)
planned.assets = append(planned.assets, componentCSS...)
planned.stylesheets = append(planned.stylesheets, componentStylesheets...)
@@ -101,7 +101,7 @@ func planCSS(config gowdk.Config, app manifest.Manifest, outputDir string) (cssP
return planned, failures
}
-func pageIDSet(pages []manifest.Page) map[string]bool {
+func pageIDSet(pages []gwdkir.Page) map[string]bool {
out := map[string]bool{}
for _, page := range pages {
out[page.ID] = true
@@ -160,7 +160,7 @@ func cssInputName(filePath string) string {
return strings.TrimSuffix(base, filepath.Ext(base))
}
-func planPageCSS(config gowdk.Config, pages []manifest.Page, outputDir string, inputs map[string]cssInput, seenAssets map[string]bool) ([]plannedCSSArtifact, map[string][]gowdk.Stylesheet, []string) {
+func planPageCSS(config gowdk.Config, pages []gwdkir.Page, outputDir string, inputs map[string]cssInput, seenAssets map[string]bool) ([]plannedCSSArtifact, map[string][]gowdk.Stylesheet, []string) {
var assets []plannedCSSArtifact
stylesheets := map[string][]gowdk.Stylesheet{}
var failures []string
@@ -199,11 +199,11 @@ func planPageCSS(config gowdk.Config, pages []manifest.Page, outputDir string, i
return assets, stylesheets, failures
}
-func planLayoutStyleCSS(pages []manifest.Page, layouts []manifest.Layout, outputDir string, seenAssets map[string]bool) ([]plannedCSSArtifact, map[string][]gowdk.Stylesheet, []string) {
+func planLayoutStyleCSS(pages []gwdkir.Page, layouts []gwdkir.Layout, outputDir string, seenAssets map[string]bool) ([]plannedCSSArtifact, map[string][]gowdk.Stylesheet, []string) {
var assets []plannedCSSArtifact
stylesheets := map[string][]gowdk.Stylesheet{}
var failures []string
- layoutRegistry := map[string]manifest.Layout{}
+ layoutRegistry := map[string]gwdkir.Layout{}
for _, layout := range layouts {
layoutRegistry[layoutRegistryKey(layout.Package, layout.ID)] = layout
}
@@ -249,7 +249,7 @@ func planLayoutStyleCSS(pages []manifest.Page, layouts []manifest.Layout, output
return assets, stylesheets, failures
}
-func planComponentCSS(components []manifest.Component, outputDir string, seenAssets map[string]bool) ([]plannedCSSArtifact, []gowdk.Stylesheet, []string) {
+func planComponentCSS(components []gwdkir.Component, outputDir string, seenAssets map[string]bool) ([]plannedCSSArtifact, []gowdk.Stylesheet, []string) {
var assets []plannedCSSArtifact
var stylesheets []gowdk.Stylesheet
var failures []string
@@ -326,7 +326,7 @@ func componentCSSSourcePath(componentSource string, cssPath string) (string, err
return filepath.Clean(filepath.Join(baseDir, filepath.FromSlash(cssPath))), nil
}
-func componentCSSLogicalPath(component manifest.Component, scopeID string) string {
+func componentCSSLogicalPath(component gwdkir.Component, scopeID string) string {
packagePart := safeCSSPathPart(component.Package)
if packagePart == "" {
packagePart = "_"
@@ -338,7 +338,7 @@ func componentCSSLogicalPath(component manifest.Component, scopeID string) strin
return path.Join(defaultPageCSSDir, "components", packagePart, componentPart, scopeID+".css")
}
-func layoutStyleLogicalPath(layout manifest.Layout, scopeID string) string {
+func layoutStyleLogicalPath(layout gwdkir.Layout, scopeID string) string {
packagePart := safeCSSPathPart(layout.Package)
if packagePart == "" {
packagePart = "_"
@@ -441,7 +441,7 @@ func rewriteStylesheets(stylesheets []gowdk.Stylesheet, hrefs map[string]string)
return out
}
-func pageCSSInputNames(config gowdk.Config, page manifest.Page, inputs map[string]cssInput) ([]string, error) {
+func pageCSSInputNames(config gowdk.Config, page gwdkir.Page, inputs map[string]cssInput) ([]string, error) {
references := page.CSS
if len(references) == 0 {
references = []string{"default", "page"}
@@ -578,9 +578,9 @@ func appendNonEmpty(values []string, patterns []string) []string {
return values
}
-func cssSources(app manifest.Manifest) []gowdk.CSSSource {
- sources := make([]gowdk.CSSSource, 0, len(app.Pages)+len(app.Components))
- for _, page := range app.Pages {
+func cssSources(ir gwdkir.Program) []gowdk.CSSSource {
+ sources := make([]gowdk.CSSSource, 0, len(ir.Pages)+len(ir.Components))
+ for _, page := range ir.Pages {
sources = append(sources, gowdk.CSSSource{
Path: page.Source,
Kind: "page",
@@ -588,7 +588,7 @@ func cssSources(app manifest.Manifest) []gowdk.CSSSource {
CSSClasses: cssClassesFromViewBody(page.Blocks.ViewBody),
})
}
- for _, component := range app.Components {
+ for _, component := range ir.Components {
sources = append(sources, gowdk.CSSSource{
Path: component.Source,
Kind: "component",
diff --git a/internal/buildgen/css_test.go b/internal/buildgen/css_test.go
index 287dd39..ece2548 100644
--- a/internal/buildgen/css_test.go
+++ b/internal/buildgen/css_test.go
@@ -467,7 +467,7 @@ func TestBuildEmitsScopedComponentCSSWithManifestAndCacheHeaders(t *testing.T) {
}
hashKey := cssscope.HashKey("component", component.Package, component.Name, component.Source, "./hero.css")
scopeID := cssscope.ScopeID(hashKey)
- logicalPath := componentCSSLogicalPath(component, scopeID)
+ logicalPath := componentCSSLogicalPath(irComponent(component), scopeID)
artifact := cssArtifactByLogicalPath(t, result.CSSArtifacts, logicalPath)
emittedRel := filepath.ToSlash(mustRelativePath(t, outputDir, artifact.Path))
if !strings.Contains(emittedRel, "/"+scopeID+".") || !strings.HasSuffix(emittedRel, ".css") {
@@ -656,7 +656,7 @@ func TestBuildEmitsScopedComponentStyleBlock(t *testing.T) {
}
hashKey := cssscope.HashKey("component", component.Package, component.Name, component.Source, inlineStyleAssetPath)
scopeID := cssscope.ScopeID(hashKey)
- artifact := cssArtifactByLogicalPath(t, result.CSSArtifacts, componentCSSLogicalPath(component, scopeID))
+ artifact := cssArtifactByLogicalPath(t, result.CSSArtifacts, componentCSSLogicalPath(irComponent(component), scopeID))
html := readFile(t, filepath.Join(outputDir, "index.html"))
emittedRel := filepath.ToSlash(mustRelativePath(t, outputDir, artifact.Path))
if !strings.Contains(html, ``) {
diff --git a/internal/buildgen/data.go b/internal/buildgen/data.go
index b7376cb..0f5f650 100644
--- a/internal/buildgen/data.go
+++ b/internal/buildgen/data.go
@@ -3,7 +3,7 @@ package buildgen
import (
"fmt"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
)
func parsePathDeclarations(body string) ([]map[string]string, error) {
@@ -14,7 +14,7 @@ func parsePathParams(source string) (map[string]string, error) {
return parseLiteralStringMap(source, "path param")
}
-func parseBuildData(body string, routeParams map[string]string, imports []manifest.Import, scripts []manifest.GoBlock, source string) (map[string]string, error) {
+func parseBuildData(body string, routeParams map[string]string, imports []gwdkir.Import, scripts []gwdkir.GoBlock, source string) (map[string]string, error) {
lines := significantBuildLines(body)
if len(lines) == 1 {
call, ok, err := parseBuildDataCallLine(lines[0])
diff --git a/internal/buildgen/gotypes_convert.go b/internal/buildgen/gotypes_convert.go
new file mode 100644
index 0000000..9ce9c3f
--- /dev/null
+++ b/internal/buildgen/gotypes_convert.go
@@ -0,0 +1,71 @@
+package buildgen
+
+import (
+ "github.com/cssbruno/gowdk/internal/gwdkir"
+ "github.com/cssbruno/gowdk/internal/manifest"
+)
+
+// The gotypes package still consumes manifest model types. buildgen now works
+// against the compiler IR (gwdkir), so these helpers translate the few IR types
+// gotypes needs (Import, GoRef/GoTypeRef, StateContract) into their manifest
+// equivalents. The conversions are field-for-field; gwdkir and manifest share
+// the same leaf types via internal/source.
+
+func manifestImports(imports []gwdkir.Import) []manifest.Import {
+ if imports == nil {
+ return nil
+ }
+ out := make([]manifest.Import, len(imports))
+ for i, item := range imports {
+ out[i] = manifest.Import{
+ Alias: item.Alias,
+ Path: item.Path,
+ Span: item.Span,
+ }
+ }
+ return out
+}
+
+func manifestGoTypeRef(ref gwdkir.GoRef) manifest.GoTypeRef {
+ return manifest.GoTypeRef{
+ Alias: ref.Alias,
+ Name: ref.Name,
+ Span: ref.Span,
+ }
+}
+
+func manifestGoFuncRef(ref gwdkir.GoRef) manifest.GoFuncRef {
+ return manifest.GoFuncRef{
+ Alias: ref.Alias,
+ Name: ref.Name,
+ Span: ref.Span,
+ }
+}
+
+func manifestStateContract(state gwdkir.StateContract) manifest.StateContract {
+ return manifest.StateContract{
+ Type: manifestGoTypeRef(state.Type),
+ Init: manifestGoFuncRef(state.Init),
+ Span: state.Span,
+ }
+}
+
+// manifestBackendBinding converts an IR page load binding into the
+// manifest.BackendBinding shape that SSRArtifact.LoadBinding exposes to appgen.
+// Only the fields appgen reads (Status, ImportPath, PackageName, FunctionName,
+// Signature) plus the binding-local metadata are populated; the kind/page/route
+// fields of manifest.BackendBinding describe a binding site that is not part of
+// a page load binding and are left empty (matching ManifestFromIR).
+func manifestBackendBinding(binding gwdkir.Binding) manifest.BackendBinding {
+ return manifest.BackendBinding{
+ ImportPath: binding.ImportPath,
+ PackageName: binding.PackageName,
+ FunctionName: binding.FunctionName,
+ Signature: binding.Signature,
+ InputType: binding.InputType,
+ InputPointer: binding.InputPointer,
+ InputFields: binding.InputFields,
+ Status: binding.Status,
+ Message: binding.Message,
+ }
+}
diff --git a/internal/buildgen/ir_test.go b/internal/buildgen/ir_test.go
index 6312e35..d47387c 100644
--- a/internal/buildgen/ir_test.go
+++ b/internal/buildgen/ir_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/cssbruno/gowdk"
+ "github.com/cssbruno/gowdk/internal/compiler"
"github.com/cssbruno/gowdk/internal/gwdkanalysis"
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
@@ -105,7 +106,7 @@ func TestBuildMemoryFromIRCollectsArtifacts(t *testing.T) {
}
func TestBuildModelFromIRPreservesFragmentEndpoints(t *testing.T) {
- app := buildModelFromIR(gwdkir.Program{
+ app := compiler.ManifestFromIR(gwdkir.Program{
Version: gwdkir.Version,
Pages: []gwdkir.Page{{
ID: "patients",
diff --git a/internal/buildgen/island_source_map.go b/internal/buildgen/island_source_map.go
index 563106f..023b75d 100644
--- a/internal/buildgen/island_source_map.go
+++ b/internal/buildgen/island_source_map.go
@@ -5,7 +5,8 @@ import (
"path"
"strings"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
+ "github.com/cssbruno/gowdk/internal/source"
)
type jsSourceMap struct {
@@ -17,7 +18,7 @@ type jsSourceMap struct {
Mappings string `json:"mappings"`
}
-func islandJSSourceMap(component manifest.Component, generatedSource string) []byte {
+func islandJSSourceMap(component gwdkir.Component, generatedSource string) []byte {
source := component.Source
if source == "" {
source = "components/" + component.Name + ".cmp.gwdk"
@@ -43,7 +44,7 @@ type sourceMapAnchor struct {
sourceColumn int
}
-func sourceMapMappings(component manifest.Component, generatedSource string) string {
+func sourceMapMappings(component gwdkir.Component, generatedSource string) string {
anchors := sourceMapAnchors(component, generatedSource)
if len(anchors) == 0 {
return ""
@@ -94,7 +95,7 @@ func sourceMapMappings(component manifest.Component, generatedSource string) str
return strings.Join(mappings, ";")
}
-func sourceMapAnchors(component manifest.Component, generatedSource string) []sourceMapAnchor {
+func sourceMapAnchors(component gwdkir.Component, generatedSource string) []sourceMapAnchor {
name := componentAssetName(component.Name)
componentSpan := firstSourceSpan(component.Span, component.Blocks.Spans.Client, component.Blocks.Spans.View)
clientSpan := firstSourceSpan(component.Blocks.Spans.Client, componentSpan)
@@ -136,7 +137,7 @@ func sourceMapLineBelongsToView(line string) bool {
strings.Contains(line, "function render(root, state, helpers, bindings)")
}
-func appendSourceMapAnchor(anchors []sourceMapAnchor, generatedLine int, span manifest.SourceSpan) []sourceMapAnchor {
+func appendSourceMapAnchor(anchors []sourceMapAnchor, generatedLine int, span source.SourceSpan) []sourceMapAnchor {
if span.Start.Line <= 0 || span.Start.Column <= 0 {
return anchors
}
@@ -147,13 +148,13 @@ func appendSourceMapAnchor(anchors []sourceMapAnchor, generatedLine int, span ma
})
}
-func firstSourceSpan(spans ...manifest.SourceSpan) manifest.SourceSpan {
+func firstSourceSpan(spans ...source.SourceSpan) source.SourceSpan {
for _, span := range spans {
if span.Start.Line > 0 && span.Start.Column > 0 {
return span
}
}
- return manifest.SourceSpan{}
+ return source.SourceSpan{}
}
const sourceMapBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
@@ -178,7 +179,7 @@ func sourceMapVLQ(value int) string {
return string(out)
}
-func componentSourceMapContent(component manifest.Component) string {
+func componentSourceMapContent(component gwdkir.Component) string {
if component.Blocks.ClientBody == "" {
return "view {\n" + component.Blocks.ViewBody + "\n}\n"
}
diff --git a/internal/buildgen/islands_test.go b/internal/buildgen/islands_test.go
index f217307..49f126d 100644
--- a/internal/buildgen/islands_test.go
+++ b/internal/buildgen/islands_test.go
@@ -358,7 +358,7 @@ func TestIslandJSSourceMapMappingsUseComponentSpans(t *testing.T) {
var sourceMap struct {
Mappings string `json:"mappings"`
}
- if err := json.Unmarshal(islandJSSourceMap(component, source), &sourceMap); err != nil {
+ if err := json.Unmarshal(islandJSSourceMap(irComponent(component), source), &sourceMap); err != nil {
t.Fatal(err)
}
if sourceMap.Mappings == "" {
diff --git a/internal/buildgen/render.go b/internal/buildgen/render.go
index 1953501..03ec23d 100644
--- a/internal/buildgen/render.go
+++ b/internal/buildgen/render.go
@@ -6,6 +6,7 @@ import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/internal/gotypes"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
"github.com/cssbruno/gowdk/internal/view"
gowhtml "github.com/cssbruno/gowdk/runtime/html"
@@ -18,7 +19,7 @@ const (
renderModeRequestTime renderModePolicy = "request-time"
)
-func renderPage(config gowdk.Config, page manifest.Page, components map[string]view.Component, layouts map[string]manifest.Layout, stylesheets []gowdk.Stylesheet, data map[string]string, policy renderModePolicy) (string, error) {
+func renderPage(config gowdk.Config, page gwdkir.Page, components map[string]view.Component, layouts map[string]gwdkir.Layout, stylesheets []gowdk.Stylesheet, data map[string]string, policy renderModePolicy) (string, error) {
mode := page.RenderMode(config.Render.DefaultMode())
if policy == renderModeSPA && mode != gowdk.SPA && mode != gowdk.Action {
return "", fmt.Errorf("%s: SPA build cannot emit request-time %s pages yet", page.ID, mode)
@@ -55,7 +56,7 @@ func renderPage(config gowdk.Config, page manifest.Page, components map[string]v
return document(config, page, body, stylesheets, storeSeeds, pageScripts(config, page, viewSource, pageComponents, policy)), nil
}
-func composePageViewSource(page manifest.Page, layouts map[string]manifest.Layout) (string, error) {
+func composePageViewSource(page gwdkir.Page, layouts map[string]gwdkir.Layout) (string, error) {
source := page.Blocks.ViewBody
if len(layouts) == 0 {
return source, nil
@@ -75,7 +76,7 @@ func composePageViewSource(page manifest.Page, layouts map[string]manifest.Layou
return source, nil
}
-func resolvePageLayout(page manifest.Page, layouts map[string]manifest.Layout, layoutRef string) (manifest.Layout, bool) {
+func resolvePageLayout(page gwdkir.Page, layouts map[string]gwdkir.Layout, layoutRef string) (gwdkir.Layout, bool) {
if alias, layoutID, ok := strings.Cut(layoutRef, "."); ok {
for _, use := range page.Uses {
if use.Alias == alias {
@@ -83,7 +84,7 @@ func resolvePageLayout(page manifest.Page, layouts map[string]manifest.Layout, l
return layout, exists
}
}
- return manifest.Layout{}, false
+ return gwdkir.Layout{}, false
}
if page.Package != "" {
if layout, ok := layouts[layoutRegistryKey(page.Package, layoutRef)]; ok {
@@ -94,7 +95,7 @@ func resolvePageLayout(page manifest.Page, layouts map[string]manifest.Layout, l
return layout, ok
}
-func composeLayoutSource(layout manifest.Layout, child string) (string, error) {
+func composeLayoutSource(layout gwdkir.Layout, child string) (string, error) {
matches := layoutSlotIndexes(layout.Blocks.ViewBody)
if len(matches) != 1 {
return "", fmt.Errorf("layout %s must contain exactly one placeholder", layout.ID)
@@ -103,7 +104,7 @@ func composeLayoutSource(layout manifest.Layout, child string) (string, error) {
return layout.Blocks.ViewBody[:match[0]] + child + layout.Blocks.ViewBody[match[1]:], nil
}
-func validateViewParamReferences(page manifest.Page, source string) error {
+func validateViewParamReferences(page gwdkir.Page, source string) error {
refs, err := view.ParamReferences(source)
if err != nil {
return err
@@ -123,7 +124,7 @@ func validateViewParamReferences(page manifest.Page, source string) error {
return nil
}
-func actionRoutes(page manifest.Page, data map[string]string) map[string]string {
+func actionRoutes(page gwdkir.Page, data map[string]string) map[string]string {
routes := map[string]string{}
for _, action := range page.Blocks.Actions {
route := action.Route
@@ -138,7 +139,7 @@ func actionRoutes(page manifest.Page, data map[string]string) map[string]string
return routes
}
-func pageScripts(config gowdk.Config, page manifest.Page, viewSource string, components map[string]view.Component, policy renderModePolicy) []gowdk.Script {
+func pageScripts(config gowdk.Config, page gwdkir.Page, viewSource string, components map[string]view.Component, policy renderModePolicy) []gowdk.Script {
scripts := append([]gowdk.Script{}, nonEmptyScripts(config.Build.Scripts)...)
for _, href := range scopedScriptHrefs(page, viewSource, components) {
scripts = append(scripts, gowdk.Script{Src: href, Type: "module"})
@@ -161,14 +162,14 @@ func pageScripts(config gowdk.Config, page manifest.Page, viewSource string, com
return scripts
}
-func pageUsesPartialRuntime(page manifest.Page, viewSource string) bool {
+func pageUsesPartialRuntime(page gwdkir.Page, viewSource string) bool {
if !strings.Contains(viewSource, "g:target") {
return false
}
return len(page.Blocks.Actions) > 0
}
-func pageUsesSPANavigationRuntime(config gowdk.Config, page manifest.Page, viewSource string, components map[string]view.Component) bool {
+func pageUsesSPANavigationRuntime(config gowdk.Config, page gwdkir.Page, viewSource string, components map[string]view.Component) bool {
mode := page.RenderMode(config.Render.DefaultMode())
if mode != gowdk.SPA && mode != gowdk.Action {
return false
@@ -232,15 +233,15 @@ type pageStoreSeed struct {
JSON string
}
-func pageStoreSeeds(page manifest.Page) ([]pageStoreSeed, error) {
+func pageStoreSeeds(page gwdkir.Page) ([]pageStoreSeed, error) {
if len(page.Stores) == 0 {
return nil, nil
}
seeds := make([]pageStoreSeed, 0, len(page.Stores))
for _, store := range page.Stores {
- payload, err := gotypes.RunStateInitJSON(page.Imports, manifest.StateContract{
- Type: store.Type,
- Init: store.Init,
+ payload, err := gotypes.RunStateInitJSON(manifestImports(page.Imports), manifest.StateContract{
+ Type: manifestGoTypeRef(store.Type),
+ Init: manifestGoFuncRef(store.Init),
Span: store.Span,
})
if err != nil {
@@ -251,7 +252,7 @@ func pageStoreSeeds(page manifest.Page) ([]pageStoreSeed, error) {
return seeds, nil
}
-func document(config gowdk.Config, page manifest.Page, body string, stylesheets []gowdk.Stylesheet, storeSeeds []pageStoreSeed, scripts []gowdk.Script) string {
+func document(config gowdk.Config, page gwdkir.Page, body string, stylesheets []gowdk.Stylesheet, storeSeeds []pageStoreSeed, scripts []gowdk.Script) string {
title := page.ID
if page.Metadata.Title != "" {
title = page.Metadata.Title
@@ -346,7 +347,7 @@ func nonEmptyScripts(scripts []gowdk.Script) []gowdk.Script {
return out
}
-func socialHeadEnabled(head gowdk.HeadConfig, metadata manifest.PageMetadata) bool {
+func socialHeadEnabled(head gowdk.HeadConfig, metadata gwdkir.PageMetadata) bool {
return head.SiteName != "" || head.Image != "" || head.TwitterCard != "" || metadata.Image != ""
}
diff --git a/internal/buildgen/report.go b/internal/buildgen/report.go
index 21725f6..1171b13 100644
--- a/internal/buildgen/report.go
+++ b/internal/buildgen/report.go
@@ -5,7 +5,7 @@ import (
"path/filepath"
"strings"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
type BuildEventLevel string
@@ -37,11 +37,11 @@ type BuildReport struct {
// BuildDiagnostic is a structured diagnostic produced during SPA planning
// or output generation after parser/compiler validation has already completed.
type BuildDiagnostic struct {
- Code string `json:"code"`
- ComponentName string `json:"componentName,omitempty"`
- Source string `json:"source,omitempty"`
- Span manifest.SourceSpan `json:"span,omitempty"`
- Message string `json:"message"`
+ Code string `json:"code"`
+ ComponentName string `json:"componentName,omitempty"`
+ Source string `json:"source,omitempty"`
+ Span source.SourceSpan `json:"span,omitempty"`
+ Message string `json:"message"`
}
type BuildError struct {
diff --git a/internal/buildgen/routes.go b/internal/buildgen/routes.go
index f755a8b..c74bf82 100644
--- a/internal/buildgen/routes.go
+++ b/internal/buildgen/routes.go
@@ -7,7 +7,7 @@ import (
"strings"
"github.com/cssbruno/gowdk"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/view"
)
@@ -16,7 +16,7 @@ type pageOutput struct {
data map[string]string
}
-func pageRouteArtifacts(outputDir string, page manifest.Page) ([]Artifact, error) {
+func pageRouteArtifacts(outputDir string, page gwdkir.Page) ([]Artifact, error) {
outputs, err := pageOutputs(page)
if err != nil {
return nil, fmt.Errorf("%s: %w", page.ID, err)
@@ -32,7 +32,7 @@ func pageRouteArtifacts(outputDir string, page manifest.Page) ([]Artifact, error
return artifacts, nil
}
-func pageOutputArtifacts(config gowdk.Config, outputDir string, page manifest.Page, components map[string]view.Component, layouts map[string]manifest.Layout, stylesheets []gowdk.Stylesheet) ([]plannedArtifact, error) {
+func pageOutputArtifacts(config gowdk.Config, outputDir string, page gwdkir.Page, components map[string]view.Component, layouts map[string]gwdkir.Layout, stylesheets []gowdk.Stylesheet) ([]plannedArtifact, error) {
outputs, err := pageOutputs(page)
if err != nil {
return nil, fmt.Errorf("%s: %w", page.ID, err)
@@ -63,7 +63,7 @@ func pageOutputArtifacts(config gowdk.Config, outputDir string, page manifest.Pa
return artifacts, nil
}
-func pageOutputs(page manifest.Page) ([]pageOutput, error) {
+func pageOutputs(page gwdkir.Page) ([]pageOutput, error) {
params := page.DynamicParams()
if len(params) == 0 {
return []pageOutput{{route: page.Route}}, nil
diff --git a/internal/buildgen/runtime_asset_paths.go b/internal/buildgen/runtime_asset_paths.go
index fb9f3d9..2023e4d 100644
--- a/internal/buildgen/runtime_asset_paths.go
+++ b/internal/buildgen/runtime_asset_paths.go
@@ -7,7 +7,7 @@ import (
"path/filepath"
"runtime"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
)
func islandWASMLoaderArtifact(outputDir, componentName string) plannedAssetArtifact {
@@ -18,7 +18,7 @@ func islandWASMLoaderArtifact(outputDir, componentName string) plannedAssetArtif
}
}
-func clientGoBlockWASMLoaderArtifact(outputDir string, page manifest.Page) plannedAssetArtifact {
+func clientGoBlockWASMLoaderArtifact(outputDir string, page gwdkir.Page) plannedAssetArtifact {
assetPath := clientGoBlockWASMLoaderAssetPath(page)
return plannedAssetArtifact{
AssetArtifact: AssetArtifact{Path: filepath.Join(outputDir, filepath.FromSlash(assetPath))},
@@ -50,11 +50,11 @@ func islandWASMLoaderAssetPath(componentName string) string {
return path.Join(islandRuntimeDir, componentAssetName(componentName)+".wasm.js")
}
-func clientGoBlockWASMAssetPath(page manifest.Page) string {
+func clientGoBlockWASMAssetPath(page gwdkir.Page) string {
return path.Join(islandRuntimeDir, "pages", clientGoBlockAssetName(page)+".wasm")
}
-func clientGoBlockWASMLoaderAssetPath(page manifest.Page) string {
+func clientGoBlockWASMLoaderAssetPath(page gwdkir.Page) string {
return path.Join(islandRuntimeDir, "pages", clientGoBlockAssetName(page)+".wasm.js")
}
@@ -74,7 +74,7 @@ func componentAssetName(componentName string) string {
return name
}
-func clientGoBlockAssetName(page manifest.Page) string {
+func clientGoBlockAssetName(page gwdkir.Page) string {
name := exportedPascalSafe(page.ID)
if name == "" {
return "Page"
@@ -82,7 +82,7 @@ func clientGoBlockAssetName(page manifest.Page) string {
return name
}
-func clientGoBlockMountExportName(page manifest.Page) string {
+func clientGoBlockMountExportName(page gwdkir.Page) string {
return "GOWDKMount" + clientGoBlockAssetName(page)
}
diff --git a/internal/buildgen/runtime_assets.go b/internal/buildgen/runtime_assets.go
index b2c909c..d4c6f39 100644
--- a/internal/buildgen/runtime_assets.go
+++ b/internal/buildgen/runtime_assets.go
@@ -5,11 +5,11 @@ import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/internal/clientrt"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/view"
)
-func clientRuntimeArtifacts(config gowdk.Config, pages []manifest.Page, outputDir string, layouts map[string]manifest.Layout, components map[string]view.Component) []plannedAssetArtifact {
+func clientRuntimeArtifacts(config gowdk.Config, pages []gwdkir.Page, outputDir string, layouts map[string]gwdkir.Layout, components map[string]view.Component) []plannedAssetArtifact {
for _, page := range pages {
viewSource := page.Blocks.ViewBody
if source, err := composePageViewSource(page, layouts); err == nil {
@@ -24,16 +24,16 @@ func clientRuntimeArtifacts(config gowdk.Config, pages []manifest.Page, outputDi
}
return nil
}
-func runtimeArtifacts(config gowdk.Config, app manifest.Manifest, outputDir string, layouts map[string]manifest.Layout, components map[string]view.Component) ([]plannedAssetArtifact, error) {
+func runtimeArtifacts(config gowdk.Config, ir gwdkir.Program, outputDir string, layouts map[string]gwdkir.Layout, components map[string]view.Component) ([]plannedAssetArtifact, error) {
var artifacts []plannedAssetArtifact
- artifacts = append(artifacts, clientRuntimeArtifacts(config, app.Pages, outputDir, layouts, components)...)
- artifacts = append(artifacts, storeRuntimeArtifacts(app.Pages, outputDir)...)
- islands, err := islandRuntimeArtifacts(config, app, outputDir, layouts)
+ artifacts = append(artifacts, clientRuntimeArtifacts(config, ir.Pages, outputDir, layouts, components)...)
+ artifacts = append(artifacts, storeRuntimeArtifacts(ir.Pages, outputDir)...)
+ islands, err := islandRuntimeArtifacts(config, ir.Pages, ir.Components, outputDir, layouts)
if err != nil {
return nil, err
}
artifacts = append(artifacts, islands...)
- clientGoBlocks, err := clientGoBlockRuntimeArtifacts(app.Pages, outputDir)
+ clientGoBlocks, err := clientGoBlockRuntimeArtifacts(ir.Pages, outputDir)
if err != nil {
return nil, err
}
@@ -41,7 +41,7 @@ func runtimeArtifacts(config gowdk.Config, app manifest.Manifest, outputDir stri
return dedupeAssetArtifacts(artifacts), nil
}
-func storeRuntimeArtifacts(pages []manifest.Page, outputDir string) []plannedAssetArtifact {
+func storeRuntimeArtifacts(pages []gwdkir.Page, outputDir string) []plannedAssetArtifact {
for _, page := range pages {
if len(page.Stores) > 0 {
return []plannedAssetArtifact{{
diff --git a/internal/buildgen/runtime_assets_test.go b/internal/buildgen/runtime_assets_test.go
index c4ee519..bfe9667 100644
--- a/internal/buildgen/runtime_assets_test.go
+++ b/internal/buildgen/runtime_assets_test.go
@@ -6,19 +6,19 @@ import (
"strings"
"testing"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
)
func TestClientGoBlockWASMSourceMergesImportsWithAST(t *testing.T) {
- page := manifest.Page{
+ page := gwdkir.Page{
ID: "counter",
- Imports: []manifest.Import{
+ Imports: []gwdkir.Import{
{Alias: "dom", Path: "syscall/js"},
{Path: "fmt"},
{Path: "example.com/unused"},
},
}
- block := manifest.GoBlock{Body: `import "strings"
+ block := gwdkir.GoBlock{Body: `import "strings"
func GOWDKMountCounter(value string) {
_ = dom.Global()
diff --git a/internal/buildgen/runtime_islands.go b/internal/buildgen/runtime_islands.go
index 5bb18f6..9403af3 100644
--- a/internal/buildgen/runtime_islands.go
+++ b/internal/buildgen/runtime_islands.go
@@ -6,15 +6,15 @@ import (
"strings"
"github.com/cssbruno/gowdk"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/view"
)
-func islandRuntimeArtifacts(config gowdk.Config, app manifest.Manifest, outputDir string, layouts map[string]manifest.Layout) ([]plannedAssetArtifact, error) {
- components := componentsByName(app.Components)
+func islandRuntimeArtifacts(config gowdk.Config, pages []gwdkir.Page, allComponents []gwdkir.Component, outputDir string, layouts map[string]gwdkir.Layout) ([]plannedAssetArtifact, error) {
+ components := componentsByName(allComponents)
includeSourceMaps := config.Build.DebugAssets()
planned := map[string]plannedAssetArtifact{}
- for _, page := range app.Pages {
+ for _, page := range pages {
source, err := composePageViewSource(page, layouts)
if err != nil {
source = page.Blocks.ViewBody
@@ -95,7 +95,7 @@ func islandScriptHrefs(source string, components map[string]view.Component, owne
return scripts
}
-func manifestComponentRuntimeMode(explicit string, component manifest.Component) string {
+func manifestComponentRuntimeMode(explicit string, component gwdkir.Component) string {
if explicit != "" {
return explicit
}
@@ -173,10 +173,10 @@ func lookupViewComponent(components map[string]view.Component, name string, owne
type resolvedManifestComponentCallUsage struct {
call view.ComponentCallUsage
- component manifest.Component
+ component gwdkir.Component
}
-func recursiveManifestComponentCallUsages(source string, components map[string]manifest.Component, ownerPackage string, uses map[string]string) ([]resolvedManifestComponentCallUsage, error) {
+func recursiveManifestComponentCallUsages(source string, components map[string]gwdkir.Component, ownerPackage string, uses map[string]string) ([]resolvedManifestComponentCallUsage, error) {
var usages []resolvedManifestComponentCallUsage
visiting := map[string]bool{}
var walk func(string, string, map[string]string) error
@@ -209,7 +209,7 @@ func recursiveManifestComponentCallUsages(source string, components map[string]m
return usages, nil
}
-func lookupManifestComponent(components map[string]manifest.Component, name string, ownerPackage string, uses map[string]string) (manifest.Component, bool) {
+func lookupManifestComponent(components map[string]gwdkir.Component, name string, ownerPackage string, uses map[string]string) (gwdkir.Component, bool) {
if strings.Contains(name, ".") {
if component, ok := components[name]; ok {
return component, true
@@ -217,7 +217,7 @@ func lookupManifestComponent(components map[string]manifest.Component, name stri
alias, componentName, _ := strings.Cut(name, ".")
packageName := uses[alias]
if packageName == "" {
- return manifest.Component{}, false
+ return gwdkir.Component{}, false
}
component, ok := components[componentRegistryKey(packageName, componentName)]
return component, ok
@@ -230,7 +230,7 @@ func lookupManifestComponent(components map[string]manifest.Component, name stri
return component, ok
}
-func statefulComponentNames(components []manifest.Component) map[string]bool {
+func statefulComponentNames(components []gwdkir.Component) map[string]bool {
out := map[string]bool{}
for _, component := range components {
if componentNeedsJSIsland(component) {
@@ -243,12 +243,12 @@ func statefulComponentNames(components []manifest.Component) map[string]bool {
return out
}
-func componentNeedsJSIsland(component manifest.Component) bool {
+func componentNeedsJSIsland(component gwdkir.Component) bool {
return component.State.Type.Name != "" || component.Blocks.Client || len(component.Emits) > 0
}
-func componentsByName(components []manifest.Component) map[string]manifest.Component {
- out := map[string]manifest.Component{}
+func componentsByName(components []gwdkir.Component) map[string]gwdkir.Component {
+ out := map[string]gwdkir.Component{}
for _, component := range components {
key := componentRegistryKey(component.Package, component.Name)
out[key] = component
@@ -307,7 +307,7 @@ func compactGeneratedJSSource(source string) string {
return strings.Join(lines, "\n") + "\n"
}
-func islandJSSourceMapArtifact(outputDir string, component manifest.Component) plannedAssetArtifact {
+func islandJSSourceMapArtifact(outputDir string, component gwdkir.Component) plannedAssetArtifact {
assetPath := islandJSSourceMapAssetPath(component.Name)
source := islandJSSource(component.Name, true)
return plannedAssetArtifact{
diff --git a/internal/buildgen/runtime_wasm_assets.go b/internal/buildgen/runtime_wasm_assets.go
index 88cac3c..2c65a55 100644
--- a/internal/buildgen/runtime_wasm_assets.go
+++ b/internal/buildgen/runtime_wasm_assets.go
@@ -15,12 +15,13 @@ import (
"strconv"
"strings"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
+ "github.com/cssbruno/gowdk/internal/source"
)
var wasmMagic = []byte{0x00, 0x61, 0x73, 0x6d}
-func clientGoBlockRuntimeArtifacts(pages []manifest.Page, outputDir string) ([]plannedAssetArtifact, error) {
+func clientGoBlockRuntimeArtifacts(pages []gwdkir.Page, outputDir string) ([]plannedAssetArtifact, error) {
planned := map[string]plannedAssetArtifact{}
for _, page := range pages {
script, ok := clientGoBlock(page)
@@ -54,14 +55,14 @@ func clientGoBlockRuntimeArtifacts(pages []manifest.Page, outputDir string) ([]p
return artifacts, nil
}
-func clientGoBlockHrefs(page manifest.Page) []string {
+func clientGoBlockHrefs(page gwdkir.Page) []string {
if _, ok := clientGoBlock(page); !ok {
return nil
}
return []string{"/" + clientGoBlockWASMLoaderAssetPath(page)}
}
-func clientGoBlock(page manifest.Page) (manifest.GoBlock, bool) {
+func clientGoBlock(page gwdkir.Page) (gwdkir.GoBlock, bool) {
required := clientGoBlockMountExportName(page)
for _, script := range page.Blocks.GoBlocks {
if script.Target != "client" {
@@ -71,10 +72,10 @@ func clientGoBlock(page manifest.Page) (manifest.GoBlock, bool) {
return script, true
}
}
- return manifest.GoBlock{}, false
+ return gwdkir.GoBlock{}, false
}
-func islandWASMArtifact(outputDir string, component manifest.Component) (plannedAssetArtifact, error) {
+func islandWASMArtifact(outputDir string, component gwdkir.Component) (plannedAssetArtifact, error) {
assetPath := islandWASMAssetPath(component.Name)
contents := []byte{0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00}
if strings.TrimSpace(component.WASM.Package) != "" {
@@ -90,7 +91,7 @@ func islandWASMArtifact(outputDir string, component manifest.Component) (planned
}, nil
}
-func buildWASMIslandPackage(component manifest.Component) ([]byte, error) {
+func buildWASMIslandPackage(component gwdkir.Component) ([]byte, error) {
packagePath := strings.TrimSpace(component.WASM.Package)
temp, err := os.CreateTemp("", "gowdk-"+componentAssetName(component.Name)+"-*.wasm")
if err != nil {
@@ -132,7 +133,7 @@ func buildWASMIslandPackage(component manifest.Component) ([]byte, error) {
return contents, nil
}
-func clientGoBlockWASMArtifact(outputDir string, page manifest.Page, script manifest.GoBlock) (plannedAssetArtifact, error) {
+func clientGoBlockWASMArtifact(outputDir string, page gwdkir.Page, script gwdkir.GoBlock) (plannedAssetArtifact, error) {
assetPath := clientGoBlockWASMAssetPath(page)
contents, err := buildClientGoBlockWASM(page, script)
if err != nil {
@@ -144,7 +145,7 @@ func clientGoBlockWASMArtifact(outputDir string, page manifest.Page, script mani
}, nil
}
-func buildClientGoBlockWASM(page manifest.Page, script manifest.GoBlock) ([]byte, error) {
+func buildClientGoBlockWASM(page gwdkir.Page, script gwdkir.GoBlock) ([]byte, error) {
temp, err := os.CreateTemp(sourceDir(page.Source), ".gowdk-"+clientGoBlockAssetName(page)+"-*.wasm")
if err != nil {
return nil, clientGoBlockDiagnosticError(page, "client_go_block_wasm_build_error", fmt.Errorf("create temp output: %w", err))
@@ -198,7 +199,7 @@ func buildClientGoBlockWASM(page manifest.Page, script manifest.GoBlock) ([]byte
return contents, nil
}
-func clientGoBlockWASMSource(page manifest.Page, script manifest.GoBlock) (string, error) {
+func clientGoBlockWASMSource(page gwdkir.Page, script gwdkir.GoBlock) (string, error) {
body := strings.TrimSpace(script.Body)
sourceWithoutImports := "package main\n" + body + "\n"
fileSet := token.NewFileSet()
@@ -232,7 +233,7 @@ func clientGoBlockWASMSource(page manifest.Page, script manifest.GoBlock) (strin
return buffer.String(), nil
}
-func clientGoBlockGOWDKImportDecl(imports []manifest.Import, file *ast.File) ast.Decl {
+func clientGoBlockGOWDKImportDecl(imports []gwdkir.Import, file *ast.File) ast.Decl {
used := usedIdentifiers(file)
localImports := map[string]bool{}
for _, spec := range importSpecs(file) {
@@ -280,7 +281,7 @@ func importSpec(alias string, importPath string) ast.Spec {
return spec
}
-func validateClientGoBlockWASMImports(page manifest.Page, sourcePath string) error {
+func validateClientGoBlockWASMImports(page gwdkir.Page, sourcePath string) error {
file, err := parser.ParseFile(token.NewFileSet(), sourcePath, nil, parser.ImportsOnly)
if err != nil {
return clientGoBlockDiagnosticError(page, "client_go_block_wasm_import_error", fmt.Errorf("parse imports: %w", err))
@@ -299,7 +300,7 @@ func validateClientGoBlockWASMImports(page manifest.Page, sourcePath string) err
return nil
}
-func validateClientGoBlockWASMExports(page manifest.Page, contents []byte) error {
+func validateClientGoBlockWASMExports(page gwdkir.Page, contents []byte) error {
exports, err := wasmExportNames(contents)
if err != nil {
return clientGoBlockDiagnosticError(page, "client_go_block_wasm_export_error", err)
@@ -311,7 +312,7 @@ func validateClientGoBlockWASMExports(page manifest.Page, contents []byte) error
return nil
}
-func validateWASMIslandExports(component manifest.Component, packagePath string, contents []byte) error {
+func validateWASMIslandExports(component gwdkir.Component, packagePath string, contents []byte) error {
exports, err := wasmExportNames(contents)
if err != nil {
return wasmIslandDiagnosticError(component, "wasm_package_export_error", packagePath, err)
@@ -428,7 +429,7 @@ func (err *wasmIslandBuildDiagnosticError) BuildDiagnostics() []BuildDiagnostic
return []BuildDiagnostic{err.diagnostic}
}
-func wasmIslandDiagnosticError(component manifest.Component, code, packagePath string, cause error) error {
+func wasmIslandDiagnosticError(component gwdkir.Component, code, packagePath string, cause error) error {
message := fmt.Sprintf("component %s wasm package %q %v", component.Name, packagePath, cause)
return &wasmIslandBuildDiagnosticError{
err: fmt.Errorf("%s", message),
@@ -468,7 +469,7 @@ func (err *clientGoBlockBuildDiagnosticError) BuildDiagnostics() []BuildDiagnost
return []BuildDiagnostic{err.diagnostic}
}
-func clientGoBlockDiagnosticError(page manifest.Page, code string, cause error) error {
+func clientGoBlockDiagnosticError(page gwdkir.Page, code string, cause error) error {
message := fmt.Sprintf("page %s go client WASM: %v", page.ID, cause)
return &clientGoBlockBuildDiagnosticError{
err: fmt.Errorf("%s", message),
@@ -481,13 +482,13 @@ func clientGoBlockDiagnosticError(page manifest.Page, code string, cause error)
}
}
-func goBlockTargetSpan(spans []manifest.NamedSpan, target string) manifest.SourceSpan {
+func goBlockTargetSpan(spans []source.NamedSpan, target string) source.SourceSpan {
for _, span := range spans {
if span.Name == target {
return span.Span
}
}
- return manifest.SourceSpan{}
+ return source.SourceSpan{}
}
var forbiddenWASMIslandImports = map[string]string{
@@ -558,7 +559,7 @@ func fileDeclaresMain(file *ast.File) bool {
return false
}
-func clientGoBlockImportAlias(item manifest.Import) string {
+func clientGoBlockImportAlias(item gwdkir.Import) string {
if strings.TrimSpace(item.Alias) != "" {
return item.Alias
}
@@ -569,7 +570,7 @@ func clientGoBlockImportAlias(item manifest.Import) string {
return path.Base(importPath)
}
-func validateWASMIslandPackageImports(component manifest.Component, dir, buildPackage, packagePath string) error {
+func validateWASMIslandPackageImports(component gwdkir.Component, dir, buildPackage, packagePath string) error {
sourceDir, ok := wasmIslandLocalSourceDir(dir, buildPackage, packagePath)
if !ok {
return nil
diff --git a/internal/buildgen/scoped_scripts.go b/internal/buildgen/scoped_scripts.go
index 180c183..43ffb76 100644
--- a/internal/buildgen/scoped_scripts.go
+++ b/internal/buildgen/scoped_scripts.go
@@ -9,7 +9,7 @@ import (
"strings"
"github.com/cssbruno/gowdk/internal/gwdkir"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/internal/view"
"github.com/evanw/esbuild/pkg/api"
)
@@ -186,7 +186,7 @@ func safeScriptAssetName(scriptPath string) string {
return name
}
-func scopedScriptHrefs(page manifest.Page, viewSource string, components map[string]view.Component) []string {
+func scopedScriptHrefs(page gwdkir.Page, viewSource string, components map[string]view.Component) []string {
seen := map[string]bool{}
var scripts []string
add := func(href string) {
@@ -202,7 +202,7 @@ func scopedScriptHrefs(page manifest.Page, viewSource string, components map[str
for index, script := range page.InlineJS {
name := script.Name
if name == "" {
- name = manifest.InlineScriptName(index)
+ name = source.InlineScriptName(index)
}
add("/" + pageScopedJSLogicalPath(page.ID, name))
}
@@ -213,7 +213,7 @@ func scopedScriptHrefs(page manifest.Page, viewSource string, components map[str
return scripts
}
-func scopedComponentScriptHrefs(page manifest.Page, viewSource string, components map[string]view.Component) []string {
+func scopedComponentScriptHrefs(page gwdkir.Page, viewSource string, components map[string]view.Component) []string {
usages, err := recursiveViewComponentCallUsages(viewSource, components, page.Package, componentUses(page.Uses))
if err != nil {
return nil
@@ -233,7 +233,7 @@ func scopedComponentScriptHrefs(page manifest.Page, viewSource string, component
for index, script := range component.InlineJS {
name := script.Name
if name == "" {
- name = manifest.InlineScriptName(index)
+ name = source.InlineScriptName(index)
}
href := "/" + componentScopedJSLogicalPath(component.Package, component.Name, name)
if seen[href] {
diff --git a/internal/buildgen/ssr.go b/internal/buildgen/ssr.go
index f68d7d0..68996a3 100644
--- a/internal/buildgen/ssr.go
+++ b/internal/buildgen/ssr.go
@@ -12,6 +12,7 @@ import (
"github.com/cssbruno/gowdk/internal/gwdkanalysis"
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/internal/view"
)
@@ -22,7 +23,7 @@ type SSRArtifact struct {
Cache string
ErrorPage string
DynamicParams []string
- RouteParams []manifest.RouteParam
+ RouteParams []source.RouteParam
Guards []string
HasLoad bool
LoadBinding manifest.BackendBinding
@@ -48,14 +49,13 @@ func SSRArtifacts(config gowdk.Config, app manifest.Manifest, outputDir string)
// SSRArtifactsFromIR renders request-time page artifacts from normalized
// compiler IR.
func SSRArtifactsFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string) ([]SSRArtifact, error) {
- app := buildModelFromIR(ir)
- if err := compiler.ValidateManifest(config, app); err != nil {
+ if err := compiler.ValidateProgram(config, ir); err != nil {
return nil, err
}
- components, componentFailures := buildComponents(app.Components)
- layouts, layoutFailures := buildLayouts(app.Layouts)
- css, cssFailures := planCSS(config, app, outputDir)
+ components, componentFailures := buildComponents(ir.Components)
+ layouts, layoutFailures := buildLayouts(ir.Layouts)
+ css, cssFailures := planCSS(config, ir, outputDir)
baseStylesheets := append([]gowdk.Stylesheet{}, config.Build.Stylesheets...)
baseStylesheets = append(baseStylesheets, css.stylesheets...)
@@ -64,7 +64,7 @@ func SSRArtifactsFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string
failures = append(failures, componentFailures...)
failures = append(failures, layoutFailures...)
failures = append(failures, cssFailures...)
- for _, page := range app.Pages {
+ for _, page := range ir.Pages {
if !isRequestTimePage(config, page) {
continue
}
@@ -81,7 +81,7 @@ func SSRArtifactsFromIR(config gowdk.Config, ir gwdkir.Program, outputDir string
return artifacts, nil
}
-func ssrArtifact(config gowdk.Config, page manifest.Page, components map[string]view.Component, layouts map[string]manifest.Layout, stylesheets []gowdk.Stylesheet) (SSRArtifact, error) {
+func ssrArtifact(config gowdk.Config, page gwdkir.Page, components map[string]view.Component, layouts map[string]gwdkir.Layout, stylesheets []gowdk.Stylesheet) (SSRArtifact, error) {
routeData, replacements := ssrRouteData(page)
buildData, err := parseBuildData(page.Blocks.BuildBody, routeData, page.Imports, page.Blocks.GoBlocks, page.Source)
if err != nil {
@@ -109,17 +109,17 @@ func ssrArtifact(config gowdk.Config, page manifest.Page, components map[string]
Cache: page.CachePolicy(),
ErrorPage: page.ErrorPage,
DynamicParams: page.DynamicParams(),
- RouteParams: append([]manifest.RouteParam(nil), page.TypedRouteParams()...),
- Guards: append([]string(nil), page.Guard...),
+ RouteParams: append([]source.RouteParam(nil), page.TypedRouteParams()...),
+ Guards: append([]string(nil), page.Guards...),
HasLoad: page.Blocks.Load,
- LoadBinding: page.LoadBinding,
+ LoadBinding: manifestBackendBinding(page.LoadBinding),
HTML: html,
Replacements: replacements,
LoadReplacements: loadReplacements,
}, nil
}
-func ssrRouteData(page manifest.Page) (map[string]string, []SSRReplacement) {
+func ssrRouteData(page gwdkir.Page) (map[string]string, []SSRReplacement) {
params := page.DynamicParams()
if len(params) == 0 {
return nil, nil
@@ -134,7 +134,7 @@ func ssrRouteData(page manifest.Page) (map[string]string, []SSRReplacement) {
return data, replacements
}
-func ssrLoadData(page manifest.Page, existing map[string]string) (map[string]string, []SSRLoadReplacement, error) {
+func ssrLoadData(page gwdkir.Page, existing map[string]string) (map[string]string, []SSRLoadReplacement, error) {
if !page.Blocks.Load {
return nil, nil, nil
}
@@ -243,7 +243,7 @@ func exportedSafe(value string) string {
return string(out)
}
-func isRequestTimePage(config gowdk.Config, page manifest.Page) bool {
+func isRequestTimePage(config gowdk.Config, page gwdkir.Page) bool {
switch page.RenderMode(config.Render.DefaultMode()) {
case gowdk.SSR:
return true
diff --git a/internal/buildgen/test_helpers_test.go b/internal/buildgen/test_helpers_test.go
index 9b14a2c..47904b2 100644
--- a/internal/buildgen/test_helpers_test.go
+++ b/internal/buildgen/test_helpers_test.go
@@ -6,9 +6,23 @@ import (
"path/filepath"
"testing"
+ "github.com/cssbruno/gowdk"
+ "github.com/cssbruno/gowdk/internal/gwdkanalysis"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
)
+// irComponent converts a manifest.Component fixture into the IR component the
+// migrated render helpers now consume. It routes through the production
+// IR builder so the test exercises the same conversion as the real pipeline.
+func irComponent(component manifest.Component) gwdkir.Component {
+ ir := gwdkanalysis.BuildIR(gowdk.Config{}, manifest.Manifest{Components: []manifest.Component{component}})
+ if len(ir.Components) == 0 {
+ return gwdkir.Component{}
+ }
+ return ir.Components[0]
+}
+
type testRouteManifest struct {
Version int `json:"version"`
Routes []struct {
diff --git a/internal/buildgen/wasm_loader_source.go b/internal/buildgen/wasm_loader_source.go
index 39cfa99..5d672f5 100644
--- a/internal/buildgen/wasm_loader_source.go
+++ b/internal/buildgen/wasm_loader_source.go
@@ -4,10 +4,10 @@ import (
"fmt"
"strconv"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
)
-func clientGoBlockWASMLoaderSource(page manifest.Page) string {
+func clientGoBlockWASMLoaderSource(page gwdkir.Page) string {
pageID := strconv.Quote(page.ID)
loaderPath := strconv.Quote("/" + clientGoBlockWASMLoaderAssetPath(page))
wasmPath := strconv.Quote("/" + clientGoBlockWASMAssetPath(page))
diff --git a/internal/compiler/backend_binding_policy.go b/internal/compiler/backend_binding_policy.go
index 865e6d3..7e05fd0 100644
--- a/internal/compiler/backend_binding_policy.go
+++ b/internal/compiler/backend_binding_policy.go
@@ -4,9 +4,19 @@ import (
"fmt"
"github.com/cssbruno/gowdk"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
+// ValidateBackendBindingPolicyIR enforces the same build-mode rules as
+// ValidateBackendBindingPolicy against an IR-first build path. IR already
+// carries per-endpoint binding metadata, so the reconstructed manifest has its
+// BackendBindings populated and the on-disk rebinding branch does not refire.
+func ValidateBackendBindingPolicyIR(config gowdk.Config, ir gwdkir.Program) error {
+ return ValidateBackendBindingPolicy(config, ManifestFromIR(ir))
+}
+
// ValidateBackendBindingPolicy enforces build-mode rules for declared backend
// endpoints after same-package Go handler binding metadata has been produced.
func ValidateBackendBindingPolicy(config gowdk.Config, app manifest.Manifest) error {
@@ -20,7 +30,7 @@ func ValidateBackendBindingPolicy(config gowdk.Config, app manifest.Manifest) er
var diagnostics []ValidationError
for _, binding := range app.BackendBindings {
switch binding.Status {
- case manifest.BackendBindingMissing, manifest.BackendBindingUnsupportedSignature:
+ case source.BackendBindingMissing, source.BackendBindingUnsupportedSignature:
diagnostics = append(diagnostics, backendBindingRequiredDiagnostic(binding))
}
}
diff --git a/internal/compiler/backend_bindings.go b/internal/compiler/backend_bindings.go
index 34d0063..99d9e02 100644
--- a/internal/compiler/backend_bindings.go
+++ b/internal/compiler/backend_bindings.go
@@ -16,6 +16,7 @@ import (
"github.com/cssbruno/gowdk/internal/goblockgen"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
const (
@@ -62,9 +63,9 @@ func BindBackendHandlers(app manifest.Manifest) manifest.Manifest {
}
for _, action := range page.Blocks.Actions {
binding := bindAction(page, action, pkg)
- if binding.Status == manifest.BackendBindingMissing {
+ if binding.Status == source.BackendBindingMissing {
inlineBinding := bindAction(page, action, defaultInlinePkg())
- if inlineBinding.Status != manifest.BackendBindingMissing {
+ if inlineBinding.Status != source.BackendBindingMissing {
binding = inlineBinding
}
}
@@ -72,9 +73,9 @@ func BindBackendHandlers(app manifest.Manifest) manifest.Manifest {
}
for _, api := range page.Blocks.APIs {
binding := bindAPI(page, api, pkg)
- if binding.Status == manifest.BackendBindingMissing {
+ if binding.Status == source.BackendBindingMissing {
inlineBinding := bindAPI(page, api, defaultInlinePkg())
- if inlineBinding.Status != manifest.BackendBindingMissing {
+ if inlineBinding.Status != source.BackendBindingMissing {
binding = inlineBinding
}
}
@@ -131,28 +132,28 @@ func bindLoad(page manifest.Page, pkg featurePackage) manifest.BackendBinding {
if function, ok := pkg.Functions[functionName]; ok {
binding := baseBackendBinding(page, loadHandlerKind, functionName, "GET", page.Route, pkg)
if !function.Load() {
- binding.Status = manifest.BackendBindingUnsupportedSignature
+ binding.Status = source.BackendBindingUnsupportedSignature
binding.Message = fmt.Sprintf("GOWDK SSR load handler %s.%s must have signature func(ssr.LoadContext) map[string]any or func(ssr.LoadContext) (map[string]any, error)", packageLabel(pkg), functionName)
return binding
}
binding.Signature = function.Signature
- binding.Status = manifest.BackendBindingBound
+ binding.Status = source.BackendBindingBound
return binding
}
inlinePkg := inspectInlineScriptFeaturePackage(page, "ssr")
if function, ok := inlinePkg.Functions[functionName]; ok {
binding := baseBackendBinding(page, loadHandlerKind, functionName, "GET", page.Route, inlinePkg)
if !function.Load() {
- binding.Status = manifest.BackendBindingUnsupportedSignature
+ binding.Status = source.BackendBindingUnsupportedSignature
binding.Message = fmt.Sprintf("GOWDK SSR load handler %s.%s must have signature func(ssr.LoadContext) map[string]any or func(ssr.LoadContext) (map[string]any, error)", packageLabel(inlinePkg), functionName)
return binding
}
binding.Signature = function.Signature
- binding.Status = manifest.BackendBindingBound
+ binding.Status = source.BackendBindingBound
return binding
}
binding := baseBackendBinding(page, loadHandlerKind, functionName, "GET", page.Route, pkg)
- binding.Status = manifest.BackendBindingMissing
+ binding.Status = source.BackendBindingMissing
binding.Message = fmt.Sprintf("GOWDK SSR load handler %s.%s is not implemented", packageLabel(pkg), functionName)
return binding
}
@@ -165,12 +166,12 @@ func bindStandaloneAction(endpoint manifest.EndpointDeclaration, pkg featurePack
binding := baseStandaloneBackendBinding(endpoint, actionHandlerKind, method, pkg)
function, ok := pkg.Functions[binding.FunctionName]
if !ok {
- binding.Status = manifest.BackendBindingMissing
+ binding.Status = source.BackendBindingMissing
binding.Message = fmt.Sprintf("GOWDK action handler %s.%s is not implemented", packageLabel(pkg), binding.FunctionName)
return binding
}
if !function.Action() {
- binding.Status = manifest.BackendBindingUnsupportedSignature
+ binding.Status = source.BackendBindingUnsupportedSignature
if function.SupportMessage != "" {
binding.Message = fmt.Sprintf("GOWDK action handler %s.%s is unsupported: %s", packageLabel(pkg), binding.FunctionName, function.SupportMessage)
} else {
@@ -182,7 +183,7 @@ func bindStandaloneAction(endpoint manifest.EndpointDeclaration, pkg featurePack
binding.InputType = function.InputType
binding.InputPointer = function.InputPointer
binding.InputFields = function.InputFields
- binding.Status = manifest.BackendBindingBound
+ binding.Status = source.BackendBindingBound
return binding
}
@@ -194,17 +195,17 @@ func bindStandaloneAPI(endpoint manifest.EndpointDeclaration, pkg featurePackage
binding := baseStandaloneBackendBinding(endpoint, apiHandlerKind, method, pkg)
function, ok := pkg.Functions[binding.FunctionName]
if !ok {
- binding.Status = manifest.BackendBindingMissing
+ binding.Status = source.BackendBindingMissing
binding.Message = fmt.Sprintf("GOWDK API handler %s.%s is not implemented", packageLabel(pkg), binding.FunctionName)
return binding
}
if !function.API() {
- binding.Status = manifest.BackendBindingUnsupportedSignature
+ binding.Status = source.BackendBindingUnsupportedSignature
binding.Message = fmt.Sprintf("GOWDK API handler %s.%s must have signature func(context.Context, *http.Request) (response.Response, error)", packageLabel(pkg), binding.FunctionName)
return binding
}
binding.Signature = function.Signature
- binding.Status = manifest.BackendBindingBound
+ binding.Status = source.BackendBindingBound
return binding
}
@@ -220,12 +221,12 @@ func bindAction(page manifest.Page, action manifest.Action, pkg featurePackage)
binding := baseBackendBinding(page, actionHandlerKind, action.Name, method, route, pkg)
function, ok := pkg.Functions[binding.FunctionName]
if !ok {
- binding.Status = manifest.BackendBindingMissing
+ binding.Status = source.BackendBindingMissing
binding.Message = fmt.Sprintf("GOWDK action handler %s.%s is not implemented", packageLabel(pkg), binding.FunctionName)
return binding
}
if !function.Action() {
- binding.Status = manifest.BackendBindingUnsupportedSignature
+ binding.Status = source.BackendBindingUnsupportedSignature
if function.SupportMessage != "" {
binding.Message = fmt.Sprintf("GOWDK action handler %s.%s is unsupported: %s", packageLabel(pkg), binding.FunctionName, function.SupportMessage)
} else {
@@ -237,7 +238,7 @@ func bindAction(page manifest.Page, action manifest.Action, pkg featurePackage)
binding.InputType = function.InputType
binding.InputPointer = function.InputPointer
binding.InputFields = function.InputFields
- binding.Status = manifest.BackendBindingBound
+ binding.Status = source.BackendBindingBound
return binding
}
@@ -253,17 +254,17 @@ func bindAPI(page manifest.Page, api manifest.API, pkg featurePackage) manifest.
binding := baseBackendBinding(page, apiHandlerKind, api.Name, method, route, pkg)
function, ok := pkg.Functions[binding.FunctionName]
if !ok {
- binding.Status = manifest.BackendBindingMissing
+ binding.Status = source.BackendBindingMissing
binding.Message = fmt.Sprintf("GOWDK API handler %s.%s is not implemented", packageLabel(pkg), binding.FunctionName)
return binding
}
if !function.API() {
- binding.Status = manifest.BackendBindingUnsupportedSignature
+ binding.Status = source.BackendBindingUnsupportedSignature
binding.Message = fmt.Sprintf("GOWDK API handler %s.%s must have signature func(context.Context, *http.Request) (response.Response, error)", packageLabel(pkg), binding.FunctionName)
return binding
}
binding.Signature = function.Signature
- binding.Status = manifest.BackendBindingBound
+ binding.Status = source.BackendBindingBound
return binding
}
@@ -278,12 +279,12 @@ func bindFragment(page manifest.Page, fragment manifest.FragmentEndpoint, pkg fe
return manifest.BackendBinding{}, false
}
if !function.Fragment() {
- binding.Status = manifest.BackendBindingUnsupportedSignature
+ binding.Status = source.BackendBindingUnsupportedSignature
binding.Message = fmt.Sprintf("GOWDK fragment handler %s.%s must have signature func(context.Context) (response.Response, error)", packageLabel(pkg), binding.FunctionName)
return binding, true
}
- binding.Signature = manifest.BackendSignatureFragment
- binding.Status = manifest.BackendBindingBound
+ binding.Signature = source.BackendSignatureFragment
+ binding.Status = source.BackendBindingBound
return binding, true
}
@@ -298,7 +299,7 @@ func baseBackendBinding(page manifest.Page, kind, blockName, method, route strin
ImportPath: pkg.ImportPath,
PackageName: pkg.Name,
FunctionName: blockName,
- Status: manifest.BackendBindingMissing,
+ Status: source.BackendBindingMissing,
}
}
@@ -313,7 +314,7 @@ func baseStandaloneBackendBinding(endpoint manifest.EndpointDeclaration, kind, m
ImportPath: pkg.ImportPath,
PackageName: pkg.Name,
FunctionName: endpoint.Name,
- Status: manifest.BackendBindingMissing,
+ Status: source.BackendBindingMissing,
}
}
@@ -327,11 +328,11 @@ func packageLabel(pkg featurePackage) string {
return "feature"
}
-func sourceDir(source string) string {
- if strings.TrimSpace(source) == "" {
+func sourceDir(sourcePath string) string {
+ if strings.TrimSpace(sourcePath) == "" {
return "."
}
- return filepath.Dir(source)
+ return filepath.Dir(sourcePath)
}
type featurePackage struct {
@@ -342,22 +343,22 @@ type featurePackage struct {
}
type inputStruct struct {
- Fields []manifest.BackendInputField
+ Fields []source.BackendInputField
Message string
}
type featureFunction struct {
Name string
- Signature manifest.BackendSignatureKind
+ Signature source.BackendSignatureKind
InputType string
InputPointer bool
- InputFields []manifest.BackendInputField
+ InputFields []source.BackendInputField
SupportMessage string
}
func (function featureFunction) Action() bool {
switch function.Signature {
- case manifest.BackendSignatureAction0, manifest.BackendSignatureActionValues, manifest.BackendSignatureActionForm, manifest.BackendSignatureActionFormPtr:
+ case source.BackendSignatureAction0, source.BackendSignatureActionValues, source.BackendSignatureActionForm, source.BackendSignatureActionFormPtr:
return true
default:
return false
@@ -365,15 +366,15 @@ func (function featureFunction) Action() bool {
}
func (function featureFunction) API() bool {
- return function.Signature == manifest.BackendSignatureAPI
+ return function.Signature == source.BackendSignatureAPI
}
func (function featureFunction) Fragment() bool {
- return function.Signature == manifest.BackendSignatureAction0 || function.Signature == manifest.BackendSignatureFragment
+ return function.Signature == source.BackendSignatureAction0 || function.Signature == source.BackendSignatureFragment
}
func (function featureFunction) Load() bool {
- return function.Signature == manifest.BackendSignatureLoad || function.Signature == manifest.BackendSignatureLoadError
+ return function.Signature == source.BackendSignatureLoad || function.Signature == source.BackendSignatureLoadError
}
func inspectFeaturePackage(dir string) featurePackage {
@@ -416,9 +417,9 @@ func inspectFeaturePackage(dir string) featurePackage {
continue
}
signature, inputType, inputPointer := backendSignature(fn.Type, imports)
- var inputFields []manifest.BackendInputField
+ var inputFields []source.BackendInputField
var supportMessage string
- if signature == manifest.BackendSignatureActionForm || signature == manifest.BackendSignatureActionFormPtr {
+ if signature == source.BackendSignatureActionForm || signature == source.BackendSignatureActionFormPtr {
inputStruct, ok := inputStructs[inputType]
if !ok {
supportMessage = fmt.Sprintf("typed action input %s must be an exported struct in the same package", inputType)
@@ -427,7 +428,7 @@ func inspectFeaturePackage(dir string) featurePackage {
supportMessage = inputStruct.Message
signature = ""
} else {
- inputFields = append([]manifest.BackendInputField(nil), inputStruct.Fields...)
+ inputFields = append([]source.BackendInputField(nil), inputStruct.Fields...)
}
}
pkg.Functions[fn.Name.Name] = featureFunction{
@@ -474,9 +475,9 @@ func inspectInlineScriptFeaturePackage(page manifest.Page, target string) featur
continue
}
signature, inputType, inputPointer := backendSignature(fn.Type, imports)
- var inputFields []manifest.BackendInputField
+ var inputFields []source.BackendInputField
var supportMessage string
- if signature == manifest.BackendSignatureActionForm || signature == manifest.BackendSignatureActionFormPtr {
+ if signature == source.BackendSignatureActionForm || signature == source.BackendSignatureActionFormPtr {
inputStruct, ok := inputStructs[inputType]
if !ok {
supportMessage = fmt.Sprintf("typed action input %s must be an exported struct in the same package", inputType)
@@ -485,7 +486,7 @@ func inspectInlineScriptFeaturePackage(page manifest.Page, target string) featur
supportMessage = inputStruct.Message
signature = ""
} else {
- inputFields = append([]manifest.BackendInputField(nil), inputStruct.Fields...)
+ inputFields = append([]source.BackendInputField(nil), inputStruct.Fields...)
}
}
pkg.Functions[fn.Name.Name] = featureFunction{
@@ -530,7 +531,7 @@ func backendInputStruct(typeName string, structType *ast.StructType) inputStruct
return inputStruct{}
}
seen := map[string]bool{}
- var fields []manifest.BackendInputField
+ var fields []source.BackendInputField
for _, field := range structType.Fields.List {
if len(field.Names) == 0 {
return inputStruct{Message: fmt.Sprintf("typed action input %s cannot use embedded fields", typeName)}
@@ -564,7 +565,7 @@ func backendInputStruct(typeName string, structType *ast.StructType) inputStruct
return inputStruct{Message: fmt.Sprintf("typed action input %s maps multiple fields to form field %q", typeName, nameFormName)}
}
seen[nameFormName] = true
- fields = append(fields, manifest.BackendInputField{
+ fields = append(fields, source.BackendInputField{
FieldName: name.Name,
FormName: nameFormName,
Type: fieldType,
@@ -690,12 +691,12 @@ func astImportAliases(file *ast.File) map[string]string {
return imports
}
-func backendSignature(function *ast.FuncType, imports map[string]string) (manifest.BackendSignatureKind, string, bool) {
+func backendSignature(function *ast.FuncType, imports map[string]string) (source.BackendSignatureKind, string, bool) {
if kind, inputType, inputPointer, ok := actionSignature(function, imports); ok {
return kind, inputType, inputPointer
}
if isAPISignature(function, imports) {
- return manifest.BackendSignatureAPI, "", false
+ return source.BackendSignatureAPI, "", false
}
if signature, ok := loadSignature(function, imports); ok {
return signature, "", false
@@ -703,7 +704,7 @@ func backendSignature(function *ast.FuncType, imports map[string]string) (manife
return "", "", false
}
-func actionSignature(function *ast.FuncType, imports map[string]string) (manifest.BackendSignatureKind, string, bool, bool) {
+func actionSignature(function *ast.FuncType, imports map[string]string) (source.BackendSignatureKind, string, bool, bool) {
if function == nil || function.Params == nil || function.Results == nil {
return "", "", false, false
}
@@ -721,18 +722,18 @@ func actionSignature(function *ast.FuncType, imports map[string]string) (manifes
return "", "", false, false
}
if len(function.Params.List) == 1 {
- return manifest.BackendSignatureAction0, "", false, true
+ return source.BackendSignatureAction0, "", false, true
}
second := function.Params.List[1].Type
if isSelector(second, imports, formImportPath, "Values") {
- return manifest.BackendSignatureActionValues, "", false, true
+ return source.BackendSignatureActionValues, "", false, true
}
if ident, ok := second.(*ast.Ident); ok && ident.IsExported() {
- return manifest.BackendSignatureActionForm, ident.Name, false, true
+ return source.BackendSignatureActionForm, ident.Name, false, true
}
if pointer, ok := second.(*ast.StarExpr); ok {
if ident, ok := pointer.X.(*ast.Ident); ok && ident.IsExported() {
- return manifest.BackendSignatureActionFormPtr, ident.Name, true, true
+ return source.BackendSignatureActionFormPtr, ident.Name, true, true
}
}
return "", "", false, false
@@ -753,7 +754,7 @@ func isAPISignature(function *ast.FuncType, imports map[string]string) bool {
isError(function.Results.List[1].Type)
}
-func loadSignature(function *ast.FuncType, imports map[string]string) (manifest.BackendSignatureKind, bool) {
+func loadSignature(function *ast.FuncType, imports map[string]string) (source.BackendSignatureKind, bool) {
if function == nil || function.Params == nil || function.Results == nil {
return "", false
}
@@ -761,10 +762,10 @@ func loadSignature(function *ast.FuncType, imports map[string]string) (manifest.
return "", false
}
if len(function.Results.List) == 1 && isMapStringAny(function.Results.List[0].Type) {
- return manifest.BackendSignatureLoad, true
+ return source.BackendSignatureLoad, true
}
if len(function.Results.List) == 2 && isMapStringAny(function.Results.List[0].Type) && isError(function.Results.List[1].Type) {
- return manifest.BackendSignatureLoadError, true
+ return source.BackendSignatureLoadError, true
}
return "", false
}
diff --git a/internal/compiler/backend_bindings_test.go b/internal/compiler/backend_bindings_test.go
index 39bdb4b..37a5040 100644
--- a/internal/compiler/backend_bindings_test.go
+++ b/internal/compiler/backend_bindings_test.go
@@ -9,6 +9,7 @@ import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/internal/goblockgen"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func TestBindBackendHandlersClassifiesSupportedActionSignatures(t *testing.T) {
@@ -103,26 +104,26 @@ func BrokenFragment(context.Context, *http.Request) (response.Response, error) {
}}})
bindings := compilerBindingsByBlock(app.BackendBindings)
- assertBinding(t, bindings["Ping"], manifest.BackendBindingBound, manifest.BackendSignatureAction0, "", false)
- assertBinding(t, bindings["Login"], manifest.BackendBindingBound, manifest.BackendSignatureActionForm, "LoginInput", false)
- assertBinding(t, bindings["LoginPtr"], manifest.BackendBindingBound, manifest.BackendSignatureActionFormPtr, "LoginInput", true)
- assertBinding(t, bindings["Raw"], manifest.BackendBindingBound, manifest.BackendSignatureActionValues, "", false)
- assertBinding(t, bindings["Session"], manifest.BackendBindingBound, manifest.BackendSignatureAPI, "", false)
+ assertBinding(t, bindings["Ping"], source.BackendBindingBound, source.BackendSignatureAction0, "", false)
+ assertBinding(t, bindings["Login"], source.BackendBindingBound, source.BackendSignatureActionForm, "LoginInput", false)
+ assertBinding(t, bindings["LoginPtr"], source.BackendBindingBound, source.BackendSignatureActionFormPtr, "LoginInput", true)
+ assertBinding(t, bindings["Raw"], source.BackendBindingBound, source.BackendSignatureActionValues, "", false)
+ assertBinding(t, bindings["Session"], source.BackendBindingBound, source.BackendSignatureAPI, "", false)
assertInputFields(t, bindings["Login"].InputFields, "Email:email:string,Tags:tag:[]string,Remember:remember:bool,Age:age:int,Score:score:uint64")
- if got := bindings["Broken"]; got.Status != manifest.BackendBindingUnsupportedSignature {
+ if got := bindings["Broken"]; got.Status != source.BackendBindingUnsupportedSignature {
t.Fatalf("expected Broken unsupported signature, got %#v", got)
}
if !strings.Contains(bindings["Broken"].Message, "unsupported field type") {
t.Fatalf("expected Broken message to explain unsupported field type, got %q", bindings["Broken"].Message)
}
- if got := bindings["Bad"]; got.Status != manifest.BackendBindingUnsupportedSignature {
+ if got := bindings["Bad"]; got.Status != source.BackendBindingUnsupportedSignature {
t.Fatalf("expected Bad unsupported signature, got %#v", got)
}
- if got := bindings["Missing"]; got.Status != manifest.BackendBindingMissing {
+ if got := bindings["Missing"]; got.Status != source.BackendBindingMissing {
t.Fatalf("expected Missing binding, got %#v", got)
}
- assertBinding(t, bindings["List"], manifest.BackendBindingBound, manifest.BackendSignatureFragment, "", false)
- if got := bindings["BrokenFragment"]; got.Status != manifest.BackendBindingUnsupportedSignature {
+ assertBinding(t, bindings["List"], source.BackendBindingBound, source.BackendSignatureFragment, "", false)
+ if got := bindings["BrokenFragment"]; got.Status != source.BackendBindingUnsupportedSignature {
t.Fatalf("expected BrokenFragment unsupported signature, got %#v", got)
}
if _, ok := bindings["MissingFragment"]; ok {
@@ -190,15 +191,15 @@ func LoadBroken() map[string]any {
}})
bindings := compilerBindingsByBlock(app.BackendBindings)
- assertBinding(t, bindings["LoadDashboard"], manifest.BackendBindingBound, manifest.BackendSignatureLoadError, "", false)
- assertBinding(t, bindings["LoadProfile"], manifest.BackendBindingBound, manifest.BackendSignatureLoad, "", false)
- if got := bindings["LoadBroken"]; got.Status != manifest.BackendBindingUnsupportedSignature {
+ assertBinding(t, bindings["LoadDashboard"], source.BackendBindingBound, source.BackendSignatureLoadError, "", false)
+ assertBinding(t, bindings["LoadProfile"], source.BackendBindingBound, source.BackendSignatureLoad, "", false)
+ if got := bindings["LoadBroken"]; got.Status != source.BackendBindingUnsupportedSignature {
t.Fatalf("expected LoadBroken unsupported signature, got %#v", got)
}
- if got := bindings["LoadMissing"]; got.Status != manifest.BackendBindingMissing {
+ if got := bindings["LoadMissing"]; got.Status != source.BackendBindingMissing {
t.Fatalf("expected LoadMissing missing binding, got %#v", got)
}
- if app.Pages[0].LoadBinding.FunctionName != "LoadDashboard" || app.Pages[0].LoadBinding.Status != manifest.BackendBindingBound {
+ if app.Pages[0].LoadBinding.FunctionName != "LoadDashboard" || app.Pages[0].LoadBinding.Status != source.BackendBindingBound {
t.Fatalf("expected page load binding to be attached, got %#v", app.Pages[0].LoadBinding)
}
}
@@ -229,7 +230,7 @@ func TestBindBackendHandlersBindsInlineSSRScriptLoad(t *testing.T) {
app := BindBackendHandlers(manifest.Manifest{Pages: []manifest.Page{page}})
bindings := compilerBindingsByBlock(app.BackendBindings)
binding := bindings["LoadDashboard"]
- if binding.Status != manifest.BackendBindingBound || binding.Signature != manifest.BackendSignatureLoadError {
+ if binding.Status != source.BackendBindingBound || binding.Signature != source.BackendSignatureLoadError {
t.Fatalf("expected inline SSR load binding, got %#v", binding)
}
if binding.ImportPath != goblockgen.GeneratedImportPath("pages") || binding.PackageName != "pages" {
@@ -294,9 +295,9 @@ func List(context.Context) (response.Response, error) {
t.Fatalf("expected %s to bind generated inline go package, got %#v", name, bindings[name])
}
}
- assertBinding(t, bindings["Subscribe"], manifest.BackendBindingBound, manifest.BackendSignatureAction0, "", false)
- assertBinding(t, bindings["Session"], manifest.BackendBindingBound, manifest.BackendSignatureAPI, "", false)
- assertBinding(t, bindings["List"], manifest.BackendBindingBound, manifest.BackendSignatureFragment, "", false)
+ assertBinding(t, bindings["Subscribe"], source.BackendBindingBound, source.BackendSignatureAction0, "", false)
+ assertBinding(t, bindings["Session"], source.BackendBindingBound, source.BackendSignatureAPI, "", false)
+ assertBinding(t, bindings["List"], source.BackendBindingBound, source.BackendSignatureFragment, "", false)
}
func TestDiscoverGoEndpointCommentsBindsStandaloneEndpoints(t *testing.T) {
@@ -341,17 +342,17 @@ func Session(context.Context, *http.Request) (response.Response, error) {
}
app = BindBackendHandlers(app)
bindings := compilerBindingsByBlock(app.BackendBindings)
- assertBinding(t, bindings["Login"], manifest.BackendBindingBound, manifest.BackendSignatureAction0, "", false)
- assertBinding(t, bindings["Session"], manifest.BackendBindingBound, manifest.BackendSignatureAPI, "", false)
+ assertBinding(t, bindings["Login"], source.BackendBindingBound, source.BackendSignatureAction0, "", false)
+ assertBinding(t, bindings["Session"], source.BackendBindingBound, source.BackendSignatureAPI, "", false)
}
func TestValidateManifestRejectsGoEndpointConflictWithGOWDKEndpoint(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
+ sourcePath := filepath.Join(root, "home.page.gwdk")
app := manifest.Manifest{
Pages: []manifest.Page{{
ID: "home",
- Source: source,
+ Source: sourcePath,
Route: "/",
Blocks: manifest.Blocks{
View: true,
@@ -410,7 +411,7 @@ func TestValidateBackendBindingPolicyAllowsDevelopmentMissingHandler(t *testing.
Method: "POST",
Route: "/login",
FunctionName: "Login",
- Status: manifest.BackendBindingMissing,
+ Status: source.BackendBindingMissing,
}}}
if err := ValidateBackendBindingPolicy(gowdk.Config{}, app); err != nil {
@@ -426,7 +427,7 @@ func TestValidateBackendBindingPolicyAllowsExplicitProductionStubMode(t *testing
Method: "GET",
Route: "/api/session",
FunctionName: "Session",
- Status: manifest.BackendBindingUnsupportedSignature,
+ Status: source.BackendBindingUnsupportedSignature,
}}}
config := gowdk.Config{Build: gowdk.BuildConfig{
@@ -438,14 +439,14 @@ func TestValidateBackendBindingPolicyAllowsExplicitProductionStubMode(t *testing
}
}
-func assertBinding(t *testing.T, binding manifest.BackendBinding, status manifest.BackendBindingStatus, signature manifest.BackendSignatureKind, inputType string, inputPointer bool) {
+func assertBinding(t *testing.T, binding manifest.BackendBinding, status source.BackendBindingStatus, signature source.BackendSignatureKind, inputType string, inputPointer bool) {
t.Helper()
if binding.Status != status || binding.Signature != signature || binding.InputType != inputType || binding.InputPointer != inputPointer {
t.Fatalf("unexpected binding: %#v", binding)
}
}
-func assertInputFields(t *testing.T, fields []manifest.BackendInputField, expected string) {
+func assertInputFields(t *testing.T, fields []source.BackendInputField, expected string) {
t.Helper()
parts := make([]string, 0, len(fields))
for _, field := range fields {
diff --git a/internal/compiler/go_endpoints.go b/internal/compiler/go_endpoints.go
index d53dc6c..7759111 100644
--- a/internal/compiler/go_endpoints.go
+++ b/internal/compiler/go_endpoints.go
@@ -11,6 +11,7 @@ import (
"strings"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
// DiscoverGoEndpointComments merges optional //gowdk:act and //gowdk:api
@@ -43,11 +44,11 @@ func DiscoverGoEndpointComments(app manifest.Manifest) (manifest.Manifest, error
func endpointSourceDirs(app manifest.Manifest) []string {
seen := map[string]bool{}
var dirs []string
- add := func(source string) {
- if strings.TrimSpace(source) == "" {
+ add := func(sourcePath string) {
+ if strings.TrimSpace(sourcePath) == "" {
return
}
- dir := sourceDir(source)
+ dir := sourceDir(sourcePath)
abs, err := filepath.Abs(dir)
if err == nil {
dir = abs
@@ -181,30 +182,30 @@ func isASCIILetters(value string) bool {
}
func goEndpointDiagnostic(fileSet *token.FileSet, path string, node ast.Node, code string, message string) ValidationError {
- var span manifest.SourceSpan
+ var span source.SourceSpan
if node != nil {
span = goTokenSpan(fileSet, node.Pos(), node.End())
}
return ValidationError{Code: code, Source: path, Span: span, Message: message}
}
-func goTokenSpan(fileSet *token.FileSet, start token.Pos, end token.Pos) manifest.SourceSpan {
+func goTokenSpan(fileSet *token.FileSet, start token.Pos, end token.Pos) source.SourceSpan {
startPos := fileSet.Position(start)
endPos := fileSet.Position(end)
- return manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: startPos.Line, Column: startPos.Column},
- End: manifest.SourcePosition{Line: endPos.Line, Column: endPos.Column},
+ return source.SourceSpan{
+ Start: source.SourcePosition{Line: startPos.Line, Column: startPos.Column},
+ End: source.SourcePosition{Line: endPos.Line, Column: endPos.Column},
}
}
-func routeParamSpansFallback(route string, fallback manifest.SourceSpan) []manifest.NamedSpan {
+func routeParamSpansFallback(route string, fallback source.SourceSpan) []source.NamedSpan {
info, issues := parseRoute(route)
if len(issues) > 0 {
return nil
}
- out := make([]manifest.NamedSpan, 0, len(info.Params))
+ out := make([]source.NamedSpan, 0, len(info.Params))
for _, param := range info.Params {
- out = append(out, manifest.NamedSpan{Name: param, Span: fallback})
+ out = append(out, source.NamedSpan{Name: param, Span: fallback})
}
return out
}
diff --git a/internal/buildgen/ir.go b/internal/compiler/manifest_from_ir.go
similarity index 58%
rename from internal/buildgen/ir.go
rename to internal/compiler/manifest_from_ir.go
index 1ab58f7..254ed55 100644
--- a/internal/buildgen/ir.go
+++ b/internal/compiler/manifest_from_ir.go
@@ -1,36 +1,55 @@
-package buildgen
+package compiler
import (
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
-func buildModelFromIR(ir gwdkir.Program) manifest.Manifest {
+// ManifestFromIR reconstructs a manifest.Manifest from compiler IR so the
+// existing manifest-typed validators can run against an IR-first build path.
+//
+// This is the single IR->manifest conversion seam in the codebase. It lives in
+// compiler (the package that owns validation) rather than being duplicated in
+// each generated-output package. As individual validators move to read IR
+// directly, this converter shrinks and is eventually removed; until then it
+// keeps the build path validating identical data whether it starts from a
+// parsed manifest or from IR.
+func ManifestFromIR(ir gwdkir.Program) manifest.Manifest {
app := manifest.Manifest{
Pages: make([]manifest.Page, 0, len(ir.Pages)),
Components: make([]manifest.Component, 0, len(ir.Components)),
Layouts: make([]manifest.Layout, 0, len(ir.Layouts)),
- BackendBindings: make([]manifest.BackendBinding, 0, len(ir.Endpoints)),
+ BackendBindings: BackendBindingsFromIR(ir),
}
for _, page := range ir.Pages {
- app.Pages = append(app.Pages, buildPageFromIR(page))
+ app.Pages = append(app.Pages, pageFromIR(page))
}
for _, component := range ir.Components {
- app.Components = append(app.Components, buildComponentFromIR(component))
+ app.Components = append(app.Components, componentFromIR(component))
}
for _, layout := range ir.Layouts {
- app.Layouts = append(app.Layouts, buildLayoutFromIR(layout))
+ app.Layouts = append(app.Layouts, layoutFromIR(layout))
}
+ return app
+}
+
+// BackendBindingsFromIR derives just the backend binding records from IR
+// endpoints, without reconstructing the full page/component/layout manifest.
+// Callers that only need bindings (e.g. build reporting) should use this instead
+// of ManifestFromIR(ir).BackendBindings, which would allocate the whole model.
+func BackendBindingsFromIR(ir gwdkir.Program) []manifest.BackendBinding {
+ out := make([]manifest.BackendBinding, 0, len(ir.Endpoints))
for _, endpoint := range ir.Endpoints {
- binding := buildBackendBindingFromIR(endpoint)
+ binding := backendBindingFromIR(endpoint)
if binding.Status != "" || binding.ImportPath != "" || binding.FunctionName != "" {
- app.BackendBindings = append(app.BackendBindings, binding)
+ out = append(out, binding)
}
}
- return app
+ return out
}
-func buildBackendBindingFromIR(endpoint gwdkir.Endpoint) manifest.BackendBinding {
+func backendBindingFromIR(endpoint gwdkir.Endpoint) manifest.BackendBinding {
kind := "action"
if endpoint.Kind == gwdkir.EndpointAPI {
kind = "api"
@@ -48,19 +67,19 @@ func buildBackendBindingFromIR(endpoint gwdkir.Endpoint) manifest.BackendBinding
Signature: endpoint.Binding.Signature,
InputType: endpoint.Binding.InputType,
InputPointer: endpoint.Binding.InputPointer,
- InputFields: append([]manifest.BackendInputField(nil), endpoint.Binding.InputFields...),
+ InputFields: append([]source.BackendInputField(nil), endpoint.Binding.InputFields...),
Status: endpoint.Binding.Status,
Message: endpoint.Binding.Message,
}
}
-func buildPageFromIR(page gwdkir.Page) manifest.Page {
+func pageFromIR(page gwdkir.Page) manifest.Page {
return manifest.Page{
Source: page.Source,
Package: page.Package,
ID: page.ID,
Route: page.Route,
- RouteParams: append([]manifest.RouteParam(nil), page.RouteParams...),
+ RouteParams: append([]source.RouteParam(nil), page.RouteParams...),
Render: page.Render,
Cache: page.Cache,
Revalidate: page.Revalidate,
@@ -70,13 +89,13 @@ func buildPageFromIR(page gwdkir.Page) manifest.Page {
Guard: append([]string(nil), page.Guards...),
CSS: append([]string(nil), page.CSS...),
JS: append([]string(nil), page.JS...),
- InlineJS: copyInlineScripts(page.InlineJS),
- Imports: buildImportsFromIR(page.Imports),
- Uses: buildUsesFromIR(page.Uses),
- Stores: buildStoresFromIR(page.Stores),
+ InlineJS: copyInlineScriptsFromIR(page.InlineJS),
+ Imports: importsFromIR(page.Imports),
+ Uses: usesFromIR(page.Uses),
+ Stores: storesFromIR(page.Stores),
Paths: page.Blocks.PathsBody != "",
- Blocks: buildBlocksFromIR(page.Blocks),
- LoadBinding: buildLoadBindingFromIR(page),
+ Blocks: blocksFromIR(page.Blocks),
+ LoadBinding: loadBindingFromIR(page),
Spans: manifest.PageSpans{
Package: page.Spans.Package,
Page: page.Spans.Page,
@@ -89,17 +108,17 @@ func buildPageFromIR(page gwdkir.Page) manifest.Page {
Description: page.Spans.Description,
Canonical: page.Spans.Canonical,
Image: page.Spans.Image,
- Layouts: append([]manifest.NamedSpan(nil), page.Spans.Layouts...),
- Guard: append([]manifest.NamedSpan(nil), page.Spans.Guard...),
- CSS: append([]manifest.NamedSpan(nil), page.Spans.CSS...),
- JS: append([]manifest.NamedSpan(nil), page.Spans.JS...),
- InlineJS: append([]manifest.NamedSpan(nil), page.Spans.InlineJS...),
- RouteParams: append([]manifest.NamedSpan(nil), page.Spans.RouteParams...),
+ Layouts: append([]source.NamedSpan(nil), page.Spans.Layouts...),
+ Guard: append([]source.NamedSpan(nil), page.Spans.Guard...),
+ CSS: append([]source.NamedSpan(nil), page.Spans.CSS...),
+ JS: append([]source.NamedSpan(nil), page.Spans.JS...),
+ InlineJS: append([]source.NamedSpan(nil), page.Spans.InlineJS...),
+ RouteParams: append([]source.NamedSpan(nil), page.Spans.RouteParams...),
},
}
}
-func buildLoadBindingFromIR(page gwdkir.Page) manifest.BackendBinding {
+func loadBindingFromIR(page gwdkir.Page) manifest.BackendBinding {
binding := page.LoadBinding
if binding.Status == "" && binding.ImportPath == "" && binding.FunctionName == "" {
return manifest.BackendBinding{}
@@ -120,57 +139,57 @@ func buildLoadBindingFromIR(page gwdkir.Page) manifest.BackendBinding {
}
}
-func buildComponentFromIR(component gwdkir.Component) manifest.Component {
+func componentFromIR(component gwdkir.Component) manifest.Component {
return manifest.Component{
Source: component.Source,
Package: component.Package,
Name: component.Name,
- Imports: buildImportsFromIR(component.Imports),
- Uses: buildUsesFromIR(component.Uses),
+ Imports: importsFromIR(component.Imports),
+ Uses: usesFromIR(component.Uses),
CSS: append([]string(nil), component.CSS...),
JS: append([]string(nil), component.JS...),
- InlineJS: copyInlineScripts(component.InlineJS),
+ InlineJS: copyInlineScriptsFromIR(component.InlineJS),
Assets: append([]string(nil), component.Assets...),
- Props: buildPropsFromIR(component.Props),
- PropsType: buildGoTypeRefFromIR(component.PropsType),
- State: buildStateContractFromIR(component.State),
+ Props: propsFromIR(component.Props),
+ PropsType: goTypeRefFromIR(component.PropsType),
+ State: stateContractFromIR(component.State),
WASM: manifest.WASMContract(component.WASM),
- Exports: buildExportsFromIR(component.Exports),
- Emits: buildEmitsFromIR(component.Emits),
- Blocks: buildBlocksFromIR(component.Blocks),
+ Exports: exportsFromIR(component.Exports),
+ Emits: emitsFromIR(component.Emits),
+ Blocks: blocksFromIR(component.Blocks),
Span: component.Span,
PackageSpan: component.PackageSpan,
Spans: manifest.ComponentSpans{
- CSS: append([]manifest.NamedSpan(nil), component.Spans.CSS...),
- JS: append([]manifest.NamedSpan(nil), component.Spans.JS...),
- InlineJS: append([]manifest.NamedSpan(nil), component.Spans.InlineJS...),
- Assets: append([]manifest.NamedSpan(nil), component.Spans.Assets...),
+ CSS: append([]source.NamedSpan(nil), component.Spans.CSS...),
+ JS: append([]source.NamedSpan(nil), component.Spans.JS...),
+ InlineJS: append([]source.NamedSpan(nil), component.Spans.InlineJS...),
+ Assets: append([]source.NamedSpan(nil), component.Spans.Assets...),
},
}
}
-func copyInlineScripts(scripts []manifest.InlineScript) []manifest.InlineScript {
+func copyInlineScriptsFromIR(scripts []source.InlineScript) []source.InlineScript {
if len(scripts) == 0 {
return nil
}
- out := make([]manifest.InlineScript, len(scripts))
+ out := make([]source.InlineScript, len(scripts))
copy(out, scripts)
return out
}
-func buildLayoutFromIR(layout gwdkir.Layout) manifest.Layout {
+func layoutFromIR(layout gwdkir.Layout) manifest.Layout {
return manifest.Layout{
Source: layout.Source,
Package: layout.Package,
ID: layout.ID,
- Uses: buildUsesFromIR(layout.Uses),
- Blocks: buildBlocksFromIR(layout.Blocks),
+ Uses: usesFromIR(layout.Uses),
+ Blocks: blocksFromIR(layout.Blocks),
Span: layout.Span,
PackageSpan: layout.PackageSpan,
}
}
-func buildBlocksFromIR(blocks gwdkir.Blocks) manifest.Blocks {
+func blocksFromIR(blocks gwdkir.Blocks) manifest.Blocks {
return manifest.Blocks{
PathsBody: blocks.PathsBody,
Build: blocks.Build,
@@ -179,32 +198,32 @@ func buildBlocksFromIR(blocks gwdkir.Blocks) manifest.Blocks {
LoadBody: blocks.LoadBody,
Client: blocks.Client,
ClientBody: blocks.ClientBody,
- GoBlocks: buildScriptsFromIR(blocks.GoBlocks),
+ GoBlocks: scriptsFromIR(blocks.GoBlocks),
View: blocks.View,
ViewBody: blocks.ViewBody,
Style: blocks.Style,
StyleBody: blocks.StyleBody,
- Actions: buildActionsFromIR(blocks.Actions),
- APIs: buildAPIsFromIR(blocks.APIs),
- Fragments: buildFragmentEndpointsFromIR(blocks.Fragments),
+ Actions: actionsFromIR(blocks.Actions),
+ APIs: apisFromIR(blocks.APIs),
+ Fragments: fragmentEndpointsFromIR(blocks.Fragments),
Spans: manifest.BlockSpans{
Paths: blocks.Spans.Paths,
Build: blocks.Spans.Build,
Load: blocks.Spans.Load,
Client: blocks.Spans.Client,
- GoBlocks: append([]manifest.NamedSpan(nil), blocks.Spans.GoBlocks...),
+ GoBlocks: append([]source.NamedSpan(nil), blocks.Spans.GoBlocks...),
View: blocks.Spans.View,
ViewBodyStart: blocks.Spans.ViewBodyStart,
- Actions: append([]manifest.NamedSpan(nil), blocks.Spans.Actions...),
- APIs: append([]manifest.NamedSpan(nil), blocks.Spans.APIs...),
- Fragments: append([]manifest.NamedSpan(nil), blocks.Spans.Fragments...),
+ Actions: append([]source.NamedSpan(nil), blocks.Spans.Actions...),
+ APIs: append([]source.NamedSpan(nil), blocks.Spans.APIs...),
+ Fragments: append([]source.NamedSpan(nil), blocks.Spans.Fragments...),
Exports: blocks.Spans.Exports,
Emits: blocks.Spans.Emits,
},
}
}
-func buildScriptsFromIR(scripts []gwdkir.GoBlock) []manifest.GoBlock {
+func scriptsFromIR(scripts []gwdkir.GoBlock) []manifest.GoBlock {
out := make([]manifest.GoBlock, 0, len(scripts))
for _, script := range scripts {
out = append(out, manifest.GoBlock{
@@ -216,7 +235,7 @@ func buildScriptsFromIR(scripts []gwdkir.GoBlock) []manifest.GoBlock {
return out
}
-func buildActionsFromIR(actions []gwdkir.Action) []manifest.Action {
+func actionsFromIR(actions []gwdkir.Action) []manifest.Action {
out := make([]manifest.Action, 0, len(actions))
for _, action := range actions {
out = append(out, manifest.Action{
@@ -228,11 +247,11 @@ func buildActionsFromIR(actions []gwdkir.Action) []manifest.Action {
InputType: action.InputType,
ValidatesInput: action.ValidatesInput,
Redirect: action.Redirect,
- Fragments: buildFragmentsFromIR(action.Fragments),
+ Fragments: fragmentsFromIR(action.Fragments),
ErrorPage: action.ErrorPage,
Span: action.Span,
RouteSpan: action.RouteSpan,
- RouteParams: append([]manifest.NamedSpan(nil), action.RouteParams...),
+ RouteParams: append([]source.NamedSpan(nil), action.RouteParams...),
InputSpan: action.InputSpan,
ValidationSpan: action.ValidationSpan,
RedirectSpan: action.RedirectSpan,
@@ -242,7 +261,7 @@ func buildActionsFromIR(actions []gwdkir.Action) []manifest.Action {
return out
}
-func buildAPIsFromIR(apis []gwdkir.API) []manifest.API {
+func apisFromIR(apis []gwdkir.API) []manifest.API {
out := make([]manifest.API, 0, len(apis))
for _, api := range apis {
out = append(out, manifest.API{
@@ -252,14 +271,14 @@ func buildAPIsFromIR(apis []gwdkir.API) []manifest.API {
ErrorPage: api.ErrorPage,
Span: api.Span,
RouteSpan: api.RouteSpan,
- RouteParams: append([]manifest.NamedSpan(nil), api.RouteParams...),
+ RouteParams: append([]source.NamedSpan(nil), api.RouteParams...),
ErrorPageSpan: api.ErrorPageSpan,
})
}
return out
}
-func buildFragmentsFromIR(fragments []gwdkir.Fragment) []manifest.Fragment {
+func fragmentsFromIR(fragments []gwdkir.Fragment) []manifest.Fragment {
out := make([]manifest.Fragment, 0, len(fragments))
for _, fragment := range fragments {
out = append(out, manifest.Fragment{Target: fragment.Target, Body: fragment.Body, Span: fragment.Span})
@@ -267,7 +286,7 @@ func buildFragmentsFromIR(fragments []gwdkir.Fragment) []manifest.Fragment {
return out
}
-func buildFragmentEndpointsFromIR(fragments []gwdkir.FragmentEndpoint) []manifest.FragmentEndpoint {
+func fragmentEndpointsFromIR(fragments []gwdkir.FragmentEndpoint) []manifest.FragmentEndpoint {
out := make([]manifest.FragmentEndpoint, 0, len(fragments))
for _, fragment := range fragments {
out = append(out, manifest.FragmentEndpoint{
@@ -279,13 +298,13 @@ func buildFragmentEndpointsFromIR(fragments []gwdkir.FragmentEndpoint) []manifes
Span: fragment.Span,
RouteSpan: fragment.RouteSpan,
TargetSpan: fragment.TargetSpan,
- RouteParams: append([]manifest.NamedSpan(nil), fragment.RouteParams...),
+ RouteParams: append([]source.NamedSpan(nil), fragment.RouteParams...),
})
}
return out
}
-func buildImportsFromIR(imports []gwdkir.Import) []manifest.Import {
+func importsFromIR(imports []gwdkir.Import) []manifest.Import {
out := make([]manifest.Import, 0, len(imports))
for _, item := range imports {
out = append(out, manifest.Import{Alias: item.Alias, Path: item.Path, Span: item.Span})
@@ -293,7 +312,7 @@ func buildImportsFromIR(imports []gwdkir.Import) []manifest.Import {
return out
}
-func buildUsesFromIR(uses []gwdkir.Use) []manifest.Use {
+func usesFromIR(uses []gwdkir.Use) []manifest.Use {
out := make([]manifest.Use, 0, len(uses))
for _, item := range uses {
out = append(out, manifest.Use{Alias: item.Alias, Package: item.Package, Span: item.Span})
@@ -301,20 +320,20 @@ func buildUsesFromIR(uses []gwdkir.Use) []manifest.Use {
return out
}
-func buildStoresFromIR(stores []gwdkir.Store) []manifest.Store {
+func storesFromIR(stores []gwdkir.Store) []manifest.Store {
out := make([]manifest.Store, 0, len(stores))
for _, store := range stores {
out = append(out, manifest.Store{
Name: store.Name,
- Type: buildGoTypeRefFromIR(store.Type),
- Init: buildGoFuncRefFromIR(store.Init),
+ Type: goTypeRefFromIR(store.Type),
+ Init: goFuncRefFromIR(store.Init),
Span: store.Span,
})
}
return out
}
-func buildPropsFromIR(props []gwdkir.Prop) []manifest.Prop {
+func propsFromIR(props []gwdkir.Prop) []manifest.Prop {
out := make([]manifest.Prop, 0, len(props))
for _, prop := range props {
out = append(out, manifest.Prop{Name: prop.Name, Type: prop.Type, Span: prop.Span})
@@ -322,7 +341,7 @@ func buildPropsFromIR(props []gwdkir.Prop) []manifest.Prop {
return out
}
-func buildExportsFromIR(exports []gwdkir.Export) []manifest.Export {
+func exportsFromIR(exports []gwdkir.Export) []manifest.Export {
out := make([]manifest.Export, 0, len(exports))
for _, export := range exports {
out = append(out, manifest.Export{Name: export.Name, Type: export.Type, Span: export.Span})
@@ -330,7 +349,7 @@ func buildExportsFromIR(exports []gwdkir.Export) []manifest.Export {
return out
}
-func buildEmitsFromIR(emits []gwdkir.Emit) []manifest.Emit {
+func emitsFromIR(emits []gwdkir.Emit) []manifest.Emit {
out := make([]manifest.Emit, 0, len(emits))
for _, emit := range emits {
params := make([]manifest.EmitParam, 0, len(emit.Params))
@@ -342,18 +361,18 @@ func buildEmitsFromIR(emits []gwdkir.Emit) []manifest.Emit {
return out
}
-func buildStateContractFromIR(state gwdkir.StateContract) manifest.StateContract {
+func stateContractFromIR(state gwdkir.StateContract) manifest.StateContract {
return manifest.StateContract{
- Type: buildGoTypeRefFromIR(state.Type),
- Init: buildGoFuncRefFromIR(state.Init),
+ Type: goTypeRefFromIR(state.Type),
+ Init: goFuncRefFromIR(state.Init),
Span: state.Span,
}
}
-func buildGoTypeRefFromIR(ref gwdkir.GoRef) manifest.GoTypeRef {
+func goTypeRefFromIR(ref gwdkir.GoRef) manifest.GoTypeRef {
return manifest.GoTypeRef{Alias: ref.Alias, Name: ref.Name, Span: ref.Span}
}
-func buildGoFuncRefFromIR(ref gwdkir.GoRef) manifest.GoFuncRef {
+func goFuncRefFromIR(ref gwdkir.GoRef) manifest.GoFuncRef {
return manifest.GoFuncRef{Alias: ref.Alias, Name: ref.Name, Span: ref.Span}
}
diff --git a/internal/compiler/manifest_from_ir_test.go b/internal/compiler/manifest_from_ir_test.go
new file mode 100644
index 0000000..add27ca
--- /dev/null
+++ b/internal/compiler/manifest_from_ir_test.go
@@ -0,0 +1,155 @@
+package compiler
+
+import (
+ "testing"
+
+ "github.com/cssbruno/gowdk"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
+ "github.com/cssbruno/gowdk/internal/source"
+)
+
+// sampleProgram returns an IR program that exercises the converter across pages,
+// components, layouts, blocks, endpoints, and bindings.
+func sampleProgram() gwdkir.Program {
+ return gwdkir.Program{
+ Version: gwdkir.Version,
+ Pages: []gwdkir.Page{{
+ Source: "pages/home.page.gwdk",
+ Package: "pages",
+ ID: "home",
+ Route: "/",
+ Render: gowdk.SPA,
+ Layouts: []string{"root"},
+ Guards: []string{"public"},
+ Blocks: gwdkir.Blocks{
+ Build: true,
+ BuildBody: `=> { title: "Home" }`,
+ View: true,
+ ViewBody: `{title}`,
+ Fragments: []gwdkir.FragmentEndpoint{{
+ Name: "List",
+ Method: "GET",
+ Route: "/home/list",
+ Target: "#list",
+ Body: "",
+ }},
+ Spans: gwdkir.BlockSpans{
+ Fragments: []source.NamedSpan{{Name: "List"}},
+ },
+ },
+ }},
+ Components: []gwdkir.Component{{
+ Source: "components/counter.cmp.gwdk",
+ Package: "components",
+ Name: "Counter",
+ Props: []gwdkir.Prop{{Name: "label", Type: "string"}},
+ }},
+ Layouts: []gwdkir.Layout{{
+ Source: "pages/root.layout.gwdk",
+ Package: "pages",
+ ID: "root",
+ Blocks: gwdkir.Blocks{
+ View: true,
+ ViewBody: ``,
+ },
+ }},
+ Endpoints: []gwdkir.Endpoint{{
+ Kind: gwdkir.EndpointAction,
+ PageID: "home",
+ SourceFile: "pages/home.page.gwdk",
+ Symbol: "Subscribe",
+ Method: "POST",
+ Path: "/subscribe",
+ Binding: gwdkir.Binding{
+ ImportPath: "example.com/app/handlers",
+ PackageName: "handlers",
+ FunctionName: "Subscribe",
+ Status: source.BackendBindingBound,
+ },
+ }},
+ }
+}
+
+// TestManifestFromIRReconstructsCoreRecords pins the converter's output shape so
+// later validator-migration steps can rely on it as the IR->manifest seam.
+func TestManifestFromIRReconstructsCoreRecords(t *testing.T) {
+ app := ManifestFromIR(sampleProgram())
+
+ if len(app.Pages) != 1 || app.Pages[0].ID != "home" || app.Pages[0].Route != "/" {
+ t.Fatalf("unexpected pages: %#v", app.Pages)
+ }
+ if len(app.Pages[0].Blocks.Fragments) != 1 || app.Pages[0].Blocks.Fragments[0].Name != "List" {
+ t.Fatalf("fragment endpoints not preserved: %#v", app.Pages[0].Blocks.Fragments)
+ }
+ if len(app.Components) != 1 || app.Components[0].Name != "Counter" {
+ t.Fatalf("unexpected components: %#v", app.Components)
+ }
+ if len(app.Layouts) != 1 || app.Layouts[0].ID != "root" {
+ t.Fatalf("unexpected layouts: %#v", app.Layouts)
+ }
+ if len(app.BackendBindings) != 1 || app.BackendBindings[0].FunctionName != "Subscribe" {
+ t.Fatalf("unexpected backend bindings: %#v", app.BackendBindings)
+ }
+ if app.BackendBindings[0].Kind != "action" || app.BackendBindings[0].Status != source.BackendBindingBound {
+ t.Fatalf("unexpected binding kind/status: %#v", app.BackendBindings[0])
+ }
+}
+
+// TestValidateProgramMatchesManifestValidation is the equivalence guard for the
+// IR-first validation path: ValidateProgram(ir) must produce the exact same
+// result as running the manifest validator on the reconstructed manifest. While
+// individual validators still read manifest, this is a tautology by
+// construction; the test exists to catch a future change that makes
+// ValidateProgram diverge from "validate the manifest produced from this IR".
+func TestValidateProgramMatchesManifestValidation(t *testing.T) {
+ config := gowdk.Config{}
+ ir := sampleProgram()
+
+ viaProgram := ValidateProgram(config, ir)
+ viaManifest := ValidateManifest(config, ManifestFromIR(ir))
+
+ if errString(viaProgram) != errString(viaManifest) {
+ t.Fatalf("ValidateProgram diverged from manifest validation:\nprogram: %v\nmanifest: %v", viaProgram, viaManifest)
+ }
+ if viaProgram != nil {
+ t.Fatalf("expected sample program to validate cleanly, got: %v", viaProgram)
+ }
+}
+
+// TestValidateProgramReportsInvalidRoutes confirms the IR-first path still
+// surfaces validation failures (here, a duplicate page identity).
+func TestValidateProgramReportsInvalidRoutes(t *testing.T) {
+ config := gowdk.Config{}
+ ir := sampleProgram()
+ ir.Pages = append(ir.Pages, ir.Pages[0]) // duplicate page id "home"
+
+ if err := ValidateProgram(config, ir); err == nil {
+ t.Fatal("expected duplicate page identity to fail IR-first validation")
+ }
+}
+
+// TestValidateBackendBindingPolicyIRMatchesManifest is the equivalence guard for
+// the production backend-binding policy on the IR-first path.
+func TestValidateBackendBindingPolicyIRMatchesManifest(t *testing.T) {
+ config := gowdk.Config{Build: gowdk.BuildConfig{Mode: gowdk.Production}}
+ ir := sampleProgram()
+ // Force an unbound endpoint so the production policy has something to reject.
+ ir.Endpoints[0].Binding.Status = source.BackendBindingMissing
+
+ viaIR := ValidateBackendBindingPolicyIR(config, ir)
+ viaManifest := ValidateBackendBindingPolicy(config, ManifestFromIR(ir))
+
+ if errString(viaIR) != errString(viaManifest) {
+ t.Fatalf("policy IR path diverged:\nir: %v\nmanifest: %v", viaIR, viaManifest)
+ }
+ if viaIR == nil {
+ t.Fatal("expected production policy to reject a missing backend binding")
+ }
+}
+
+func errString(err error) string {
+ if err == nil {
+ return ""
+ }
+ return err.Error()
+}
diff --git a/internal/compiler/route_bindings.go b/internal/compiler/route_bindings.go
index 467b77e..7347020 100644
--- a/internal/compiler/route_bindings.go
+++ b/internal/compiler/route_bindings.go
@@ -9,6 +9,7 @@ import (
"github.com/cssbruno/gowdk/internal/gwdkanalysis"
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
// RouteKind describes route behavior in the CLI routes report.
@@ -57,7 +58,7 @@ type EndpointBinding struct {
Kind EndpointKind
EndpointSource string
Source string
- SourceSpan manifest.SourceSpan
+ SourceSpan source.SourceSpan
Package string
PackagePath string
PackageName string
@@ -66,12 +67,12 @@ type EndpointBinding struct {
Route string
PageID string
Handler string
- BindingStatus manifest.BackendBindingStatus
+ BindingStatus source.BackendBindingStatus
BindingMessage string
BindingImportPath string
BindingPackage string
BindingFunction string
- BindingSignature manifest.BackendSignatureKind
+ BindingSignature source.BackendSignatureKind
BindingInputType string
Contract ContractEndpointBinding
}
diff --git a/internal/compiler/route_bindings_test.go b/internal/compiler/route_bindings_test.go
index 0cc8006..eb1d4e3 100644
--- a/internal/compiler/route_bindings_test.go
+++ b/internal/compiler/route_bindings_test.go
@@ -7,6 +7,7 @@ import (
"github.com/cssbruno/gowdk/addons/ssr"
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func TestBuildRouteMetadataSeparatesRoutesFromEndpoints(t *testing.T) {
@@ -143,11 +144,11 @@ func TestBuildRouteMetadataFromIR(t *testing.T) {
Path: "/newsletter",
SourceFile: "newsletter.page.gwdk",
Binding: gwdkir.Binding{
- Status: manifest.BackendBindingBound,
+ Status: source.BackendBindingBound,
ImportPath: "example.com/app/newsletter",
PackageName: "newsletter",
FunctionName: "Subscribe",
- Signature: manifest.BackendSignatureAction0,
+ Signature: source.BackendSignatureAction0,
},
},
{
@@ -184,7 +185,7 @@ func TestBuildRouteMetadataFromIR(t *testing.T) {
assertEndpoint(t, metadata.Endpoints, EndpointAction, "POST", "/newsletter", "actions.NewsletterSubscribe")
assertEndpoint(t, metadata.Endpoints, EndpointFragment, "GET", "/newsletter/list", "fragments.NewsletterList")
assertEndpoint(t, metadata.Endpoints, EndpointCommand, "POST", "/patients", "contracts.command.patients.CreatePatient")
- if metadata.Endpoints[0].BindingStatus != manifest.BackendBindingBound {
+ if metadata.Endpoints[0].BindingStatus != source.BackendBindingBound {
t.Fatalf("expected binding status from IR, got %#v", metadata.Endpoints[0])
}
command := findEndpoint(t, metadata.Endpoints, EndpointCommand, "POST", "/patients")
diff --git a/internal/compiler/routes.go b/internal/compiler/routes.go
index 2a00c37..b7d0132 100644
--- a/internal/compiler/routes.go
+++ b/internal/compiler/routes.go
@@ -6,6 +6,7 @@ import (
"unicode"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func validateUniquePageRoutes(pages []manifest.Page) []ValidationError {
@@ -162,7 +163,7 @@ type routeRegistration struct {
Pattern string
PageID string
Source string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
func routeRegistrations(pages []manifest.Page, endpoints []manifest.EndpointDeclaration) []routeRegistration {
@@ -325,7 +326,7 @@ func standaloneEndpointPageID(endpoint manifest.EndpointDeclaration) string {
return endpoint.Package + "." + endpoint.Name
}
-func routeDiagnostics(page manifest.Page, label string, issues []routeIssue, routeSpan manifest.SourceSpan, paramSpans []manifest.NamedSpan) []ValidationError {
+func routeDiagnostics(page manifest.Page, label string, issues []routeIssue, routeSpan source.SourceSpan, paramSpans []source.NamedSpan) []ValidationError {
if len(issues) == 0 {
return nil
}
@@ -359,7 +360,7 @@ type routeIssue struct {
ParamOccurrence int
}
-func routeIssueSpan(issue routeIssue, routeSpan manifest.SourceSpan, paramSpans []manifest.NamedSpan) manifest.SourceSpan {
+func routeIssueSpan(issue routeIssue, routeSpan source.SourceSpan, paramSpans []source.NamedSpan) source.SourceSpan {
if issue.Param != "" {
if issue.ParamOccurrence > 1 {
return spanForNameOccurrence(paramSpans, issue.Param, issue.ParamOccurrence, routeSpan)
diff --git a/internal/compiler/validate.go b/internal/compiler/validate.go
index 0b8e30f..a7bbecb 100644
--- a/internal/compiler/validate.go
+++ b/internal/compiler/validate.go
@@ -3,7 +3,9 @@ package compiler
import (
"fmt"
"github.com/cssbruno/gowdk"
+ "github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
type ValidationError struct {
@@ -11,7 +13,7 @@ type ValidationError struct {
PageID string
ComponentName string
Source string
- Span manifest.SourceSpan
+ Span source.SourceSpan
Message string
}
@@ -52,3 +54,12 @@ func ValidateManifest(config gowdk.Config, app manifest.Manifest) error {
}
return ValidationErrors(diagnostics)
}
+
+// ValidateProgram checks the same render-mode invariants as ValidateManifest
+// against an IR-first build path. It reconstructs the manifest from IR via
+// ManifestFromIR so generated-output packages no longer need to carry their own
+// IR->manifest converter. As validators move to read IR directly, the converter
+// shrinks until this entrypoint reads IR with no manifest intermediary.
+func ValidateProgram(config gowdk.Config, ir gwdkir.Program) error {
+ return ValidateManifest(config, ManifestFromIR(ir))
+}
diff --git a/internal/compiler/validate_component_client.go b/internal/compiler/validate_component_client.go
index de7bc6d..999dcb5 100644
--- a/internal/compiler/validate_component_client.go
+++ b/internal/compiler/validate_component_client.go
@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/cssbruno/gowdk/internal/clientlang"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/internal/view"
"strings"
)
@@ -246,7 +247,7 @@ func componentEmitMap(component manifest.Component) map[string]clientlang.Emit {
return out
}
-func clientStatementErrorSpan(component manifest.Component, statements []string, spans []clientlang.Span, err error) manifest.SourceSpan {
+func clientStatementErrorSpan(component manifest.Component, statements []string, spans []clientlang.Span, err error) source.SourceSpan {
var statementErr view.StatementValidationError
if errors.As(err, &statementErr) && statementErr.Index >= 0 && statementErr.Index < len(spans) {
if statementErr.Index < len(statements) {
@@ -257,7 +258,7 @@ func clientStatementErrorSpan(component manifest.Component, statements []string,
return firstSpan(component.Blocks.Spans.Client, component.Span)
}
-func clientParseErrorSpan(component manifest.Component, err error) manifest.SourceSpan {
+func clientParseErrorSpan(component manifest.Component, err error) source.SourceSpan {
var parseErr *clientlang.ParseError
if errors.As(err, &parseErr) && parseErr.Line > 0 {
return clientSpan(component, clientlang.Span{StartLine: parseErr.Line, EndLine: parseErr.Line})
@@ -265,7 +266,7 @@ func clientParseErrorSpan(component manifest.Component, err error) manifest.Sour
return firstSpan(component.Blocks.Spans.Client, component.Span)
}
-func clientExpressionErrorSpan(component manifest.Component, statement string, span clientlang.Span, err error) manifest.SourceSpan {
+func clientExpressionErrorSpan(component manifest.Component, statement string, span clientlang.Span, err error) source.SourceSpan {
var exprErr clientlang.ExprValidationError
if !errors.As(err, &exprErr) || exprErr.Span.StartColumn <= 0 {
return clientSpan(component, span)
@@ -299,11 +300,11 @@ func functionReturnSpan(function clientlang.Function) clientlang.Span {
return function.StatementSpans[len(function.StatementSpans)-1]
}
-func clientSpan(component manifest.Component, span clientlang.Span) manifest.SourceSpan {
+func clientSpan(component manifest.Component, span clientlang.Span) source.SourceSpan {
return clientSpanColumns(component, span, 1, 2)
}
-func clientSpanColumns(component manifest.Component, span clientlang.Span, startColumn, endColumn int) manifest.SourceSpan {
+func clientSpanColumns(component manifest.Component, span clientlang.Span, startColumn, endColumn int) source.SourceSpan {
if span.StartLine <= 0 {
return firstSpan(component.Blocks.Spans.Client, component.Span)
}
@@ -322,9 +323,9 @@ func clientSpanColumns(component manifest.Component, span clientlang.Span, start
if endColumn <= startColumn {
endColumn = startColumn + 1
}
- return manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: startLine, Column: startColumn},
- End: manifest.SourcePosition{Line: endLine, Column: endColumn},
+ return source.SourceSpan{
+ Start: source.SourcePosition{Line: startLine, Column: startColumn},
+ End: source.SourcePosition{Line: endLine, Column: endColumn},
}
}
diff --git a/internal/compiler/validate_component_contracts.go b/internal/compiler/validate_component_contracts.go
index 4e7cc21..5c1cbe3 100644
--- a/internal/compiler/validate_component_contracts.go
+++ b/internal/compiler/validate_component_contracts.go
@@ -6,6 +6,7 @@ import (
"github.com/cssbruno/gowdk/internal/clientlang"
"github.com/cssbruno/gowdk/internal/gotypes"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"strings"
)
@@ -166,7 +167,7 @@ func validateComponentImports(component manifest.Component) []ValidationError {
return diagnostics
}
-func componentContractDiagnostic(component manifest.Component, code string, span manifest.SourceSpan, err error) ValidationError {
+func componentContractDiagnostic(component manifest.Component, code string, span source.SourceSpan, err error) ValidationError {
return ValidationError{
Code: code,
ComponentName: component.Name,
@@ -176,13 +177,13 @@ func componentContractDiagnostic(component manifest.Component, code string, span
}
}
-func importSource(source string, item manifest.Import) string {
- if source == "" {
+func importSource(sourcePath string, item manifest.Import) string {
+ if sourcePath == "" {
return ""
}
name := item.Alias
if strings.TrimSpace(name) == "" {
name = item.Path
}
- return fmt.Sprintf("%s import %s", source, name)
+ return fmt.Sprintf("%s import %s", sourcePath, name)
}
diff --git a/internal/compiler/validate_component_lists.go b/internal/compiler/validate_component_lists.go
index 4d5e9d5..0e6ac0d 100644
--- a/internal/compiler/validate_component_lists.go
+++ b/internal/compiler/validate_component_lists.go
@@ -6,6 +6,7 @@ import (
"github.com/cssbruno/gowdk/internal/clientlang"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/internal/view"
)
@@ -34,7 +35,7 @@ func validateComponentListDirectives(component manifest.Component, symbols map[s
type spannedMessage struct {
Message string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
func validateListNodes(nodes []view.Node, component manifest.Component, symbols map[string]clientlang.ValueType, stateTypes map[string]clientlang.ValueType, handlers map[string]clientlang.Handler, helpers map[string]clientlang.ExprFunction, messages *[]spannedMessage) {
diff --git a/internal/compiler/validate_contract_refs_test.go b/internal/compiler/validate_contract_refs_test.go
index edd06e8..89b402b 100644
--- a/internal/compiler/validate_contract_refs_test.go
+++ b/internal/compiler/validate_contract_refs_test.go
@@ -5,7 +5,7 @@ import (
"testing"
"github.com/cssbruno/gowdk/internal/gwdkir"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func TestValidateContractReferencesRejectsBoundNonWebRole(t *testing.T) {
@@ -17,8 +17,8 @@ func TestValidateContractReferencesRejectsBoundNonWebRole(t *testing.T) {
OwnerKind: gwdkir.SourcePage,
OwnerID: "patients",
Source: "patients.page.gwdk",
- Span: manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: 8, Column: 42},
+ Span: source.SourceSpan{
+ Start: source.SourcePosition{Line: 8, Column: 42},
},
}})
if err == nil {
diff --git a/internal/compiler/validate_packages.go b/internal/compiler/validate_packages.go
index a25d850..339cf68 100644
--- a/internal/compiler/validate_packages.go
+++ b/internal/compiler/validate_packages.go
@@ -19,6 +19,7 @@ import (
"strings"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
type packageDeclaration struct {
@@ -29,7 +30,7 @@ type packageDeclaration struct {
Package string
Imports []manifest.Import
GoBlocks []manifest.GoBlock
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type goPackageInfo struct {
@@ -91,12 +92,12 @@ func validatePackages(app manifest.Manifest) []ValidationError {
return diagnostics
}
-func shouldValidatePackageSource(source string) bool {
- source = strings.TrimSpace(source)
- if source == "" {
+func shouldValidatePackageSource(sourcePath string) bool {
+ sourcePath = strings.TrimSpace(sourcePath)
+ if sourcePath == "" {
return false
}
- if _, err := os.Stat(source); err == nil {
+ if _, err := os.Stat(sourcePath); err == nil {
return true
}
return false
@@ -256,11 +257,11 @@ func inspectGoPackageForValidation(dir string, group []packageDeclaration) goPac
}
func parseGoBlockPackageFileForValidation(fileSet *token.FileSet, declaration packageDeclaration, block manifest.GoBlock) (*ast.File, *ValidationError) {
- source, err := goBlockPackageSourceForValidation(declaration, block)
+ src, err := goBlockPackageSourceForValidation(declaration, block)
if err != nil {
return nil, nil
}
- file, parseErr := parser.ParseFile(fileSet, declaration.Source, source, parser.AllErrors|parser.ParseComments)
+ file, parseErr := parser.ParseFile(fileSet, declaration.Source, src, parser.AllErrors|parser.ParseComments)
if parseErr != nil {
diagnostic := ValidationError{
Code: "invalid_go_block",
@@ -355,13 +356,13 @@ func goBlockImportSpecSortKey(spec ast.Spec) string {
return alias + "\x00" + path
}
-func addGoBlockLineDirectiveForValidation(fileSet *token.FileSet, file *ast.File, source string, line int) *ast.CommentGroup {
+func addGoBlockLineDirectiveForValidation(fileSet *token.FileSet, file *ast.File, sourcePath string, line int) *ast.CommentGroup {
if len(file.Decls) == 0 {
return nil
}
directive := &ast.Comment{
Slash: goBlockLineDirectivePosition(fileSet, file.Decls[0].Pos()),
- Text: "//line " + source + ":" + strconv.Itoa(line),
+ Text: "//line " + sourcePath + ":" + strconv.Itoa(line),
}
group := &ast.CommentGroup{List: []*ast.Comment{directive}}
switch decl := file.Decls[0].(type) {
@@ -555,9 +556,9 @@ func goTypeCheckDiagnostic(fileSet *token.FileSet, err error) ValidationError {
position := fileSet.PositionFor(typeError.Pos, true)
if position.IsValid() {
diagnostic.Source = position.Filename
- diagnostic.Span = manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: position.Line, Column: position.Column},
- End: manifest.SourcePosition{Line: position.Line, Column: position.Column + 1},
+ diagnostic.Span = source.SourceSpan{
+ Start: source.SourcePosition{Line: position.Line, Column: position.Column},
+ End: source.SourcePosition{Line: position.Line, Column: position.Column + 1},
}
}
return diagnostic
@@ -570,8 +571,8 @@ func isGoPackageSource(name string) bool {
return strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go")
}
-func packageSourceDir(source string) string {
- dir := filepath.Dir(source)
+func packageSourceDir(sourcePath string) string {
+ dir := filepath.Dir(sourcePath)
abs, err := filepath.Abs(dir)
if err != nil {
return dir
@@ -579,9 +580,9 @@ func packageSourceDir(source string) string {
return abs
}
-func sourceLabel(source string, fallback string) string {
- if strings.TrimSpace(source) == "" {
+func sourceLabel(sourcePath string, fallback string) string {
+ if strings.TrimSpace(sourcePath) == "" {
return fallback
}
- return filepath.Base(source)
+ return filepath.Base(sourcePath)
}
diff --git a/internal/compiler/validate_page.go b/internal/compiler/validate_page.go
index a64eb87..8fe921e 100644
--- a/internal/compiler/validate_page.go
+++ b/internal/compiler/validate_page.go
@@ -8,6 +8,7 @@ import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/internal/gotypes"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/runtime/auth"
)
@@ -240,13 +241,13 @@ func hasProtectedPageGuard(page manifest.Page) bool {
return false
}
-func firstGoBlockSpan(page manifest.Page, target string) manifest.SourceSpan {
+func firstGoBlockSpan(page manifest.Page, target string) source.SourceSpan {
for _, block := range page.Blocks.GoBlocks {
if block.Target == target {
return block.Span
}
}
- return manifest.SourceSpan{}
+ return source.SourceSpan{}
}
func requiresSSRFeature(mode gowdk.RenderMode, page manifest.Page) bool {
diff --git a/internal/compiler/validate_scripts.go b/internal/compiler/validate_scripts.go
index 1745657..b926fa4 100644
--- a/internal/compiler/validate_scripts.go
+++ b/internal/compiler/validate_scripts.go
@@ -8,6 +8,7 @@ import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func validateGoBlocks(config gowdk.Config, app manifest.Manifest) []ValidationError {
@@ -35,7 +36,7 @@ func validateGoBlocks(config gowdk.Config, app manifest.Manifest) []ValidationEr
return diagnostics
}
-func validateGoBlockSyntax(packageName string, source string, pageID string, componentName string, block manifest.GoBlock) []ValidationError {
+func validateGoBlockSyntax(packageName string, sourcePath string, pageID string, componentName string, block manifest.GoBlock) []ValidationError {
if strings.TrimSpace(block.Body) == "" {
return nil
}
@@ -50,13 +51,13 @@ func validateGoBlockSyntax(packageName string, source string, pageID string, com
Code: "invalid_go_block",
PageID: pageID,
ComponentName: componentName,
- Source: source,
+ Source: sourcePath,
Span: block.Span,
Message: fmt.Sprintf("go %s contains invalid Go: %v", goBlockLabel(block.Target), err),
}}
}
-func validateGoBlockTarget(config gowdk.Config, enabledAddons map[string]gowdk.Addon, pageID string, componentName string, source string, packageName string, mode gowdk.RenderMode, hasLoad bool, block manifest.GoBlock) []ValidationError {
+func validateGoBlockTarget(config gowdk.Config, enabledAddons map[string]gowdk.Addon, pageID string, componentName string, sourcePath string, packageName string, mode gowdk.RenderMode, hasLoad bool, block manifest.GoBlock) []ValidationError {
target := strings.TrimSpace(block.Target)
switch {
case target == "" || target == "client":
@@ -69,25 +70,25 @@ func validateGoBlockTarget(config gowdk.Config, enabledAddons map[string]gowdk.A
Code: "go_ssr_requires_request_render",
PageID: pageID,
ComponentName: componentName,
- Source: source,
+ Source: sourcePath,
Span: block.Span,
Message: fmt.Sprintf("%s declares go ssr {}, but ssr Go code is request-time behavior and requires the SSR addon", pageID),
}}
case strings.HasPrefix(target, "addon."):
- return validateAddonGoBlockTarget(enabledAddons, pageID, componentName, source, packageName, mode, block)
+ return validateAddonGoBlockTarget(enabledAddons, pageID, componentName, sourcePath, packageName, mode, block)
default:
return []ValidationError{{
Code: "unknown_go_block_target",
PageID: pageID,
ComponentName: componentName,
- Source: source,
+ Source: sourcePath,
Span: block.Span,
Message: fmt.Sprintf("unknown go block target %q; use go {}, go client {}, go ssr {}, or go addon. {}", target),
}}
}
}
-func validateNonPageGoBlockTarget(config gowdk.Config, enabledAddons map[string]gowdk.Addon, pageID string, componentName string, source string, packageName string, block manifest.GoBlock) []ValidationError {
+func validateNonPageGoBlockTarget(config gowdk.Config, enabledAddons map[string]gowdk.Addon, pageID string, componentName string, sourcePath string, packageName string, block manifest.GoBlock) []ValidationError {
target := strings.TrimSpace(block.Target)
switch {
case target == "":
@@ -97,7 +98,7 @@ func validateNonPageGoBlockTarget(config gowdk.Config, enabledAddons map[string]
Code: "go_client_requires_page",
PageID: pageID,
ComponentName: componentName,
- Source: source,
+ Source: sourcePath,
Span: block.Span,
Message: "go client {} is page-level client-side behavior; use a page go client {} block or a component @wasm package",
}}
@@ -109,25 +110,25 @@ func validateNonPageGoBlockTarget(config gowdk.Config, enabledAddons map[string]
Code: "missing_ssr_addon",
PageID: pageID,
ComponentName: componentName,
- Source: source,
+ Source: sourcePath,
Span: block.Span,
Message: "go ssr {} requires the SSR addon before request-time Go code can be validated or generated",
}}
case strings.HasPrefix(target, "addon."):
- return validateAddonGoBlockTarget(enabledAddons, pageID, componentName, source, packageName, gowdk.SPA, block)
+ return validateAddonGoBlockTarget(enabledAddons, pageID, componentName, sourcePath, packageName, gowdk.SPA, block)
default:
return []ValidationError{{
Code: "unknown_go_block_target",
PageID: pageID,
ComponentName: componentName,
- Source: source,
+ Source: sourcePath,
Span: block.Span,
Message: fmt.Sprintf("unknown go block target %q; use go {}, go client {}, go ssr {}, or go addon. {}", target),
}}
}
}
-func validateAddonGoBlockTarget(enabledAddons map[string]gowdk.Addon, pageID string, componentName string, source string, packageName string, render gowdk.RenderMode, block manifest.GoBlock) []ValidationError {
+func validateAddonGoBlockTarget(enabledAddons map[string]gowdk.Addon, pageID string, componentName string, sourcePath string, packageName string, render gowdk.RenderMode, block manifest.GoBlock) []ValidationError {
name := strings.TrimPrefix(strings.TrimSpace(block.Target), "addon.")
addon, ok := enabledAddons[name]
if name != "" && ok {
@@ -140,18 +141,18 @@ func validateAddonGoBlockTarget(enabledAddons map[string]gowdk.Addon, pageID str
Code: "unsupported_addon_go_block_target",
PageID: pageID,
ComponentName: componentName,
- Source: source,
+ Source: sourcePath,
Span: block.Span,
Message: fmt.Sprintf("addon %q does not consume go block target %q", name, block.Target),
}}
}
- return addonGoBlockDiagnostics(consumer, pageID, componentName, source, packageName, render, block)
+ return addonGoBlockDiagnostics(consumer, pageID, componentName, sourcePath, packageName, render, block)
}
return []ValidationError{{
Code: "unknown_addon_go_block_target",
PageID: pageID,
ComponentName: componentName,
- Source: source,
+ Source: sourcePath,
Span: block.Span,
Message: fmt.Sprintf("go addon.%s {} requires an enabled addon named %q", name, name),
}}
@@ -166,8 +167,8 @@ func goBlockConsumerSupportsTarget(consumer gowdk.GoBlockConsumer, target string
return false
}
-func addonGoBlockDiagnostics(consumer gowdk.GoBlockConsumer, pageID string, componentName string, source string, packageName string, render gowdk.RenderMode, block manifest.GoBlock) []ValidationError {
- target := gowdkGoBlockTarget(pageID, componentName, source, packageName, block)
+func addonGoBlockDiagnostics(consumer gowdk.GoBlockConsumer, pageID string, componentName string, sourcePath string, packageName string, render gowdk.RenderMode, block manifest.GoBlock) []ValidationError {
+ target := gowdkGoBlockTarget(pageID, componentName, sourcePath, packageName, block)
context := gowdk.GoBlockContext{Render: render}
var diagnostics []ValidationError
for _, diagnostic := range consumer.ValidateGoBlock(target, context) {
@@ -183,7 +184,7 @@ func addonGoBlockDiagnostics(consumer gowdk.GoBlockConsumer, pageID string, comp
Code: code,
PageID: pageID,
ComponentName: componentName,
- Source: source,
+ Source: sourcePath,
Span: span,
Message: diagnostic.Message,
})
@@ -199,7 +200,7 @@ func addonsByName(config gowdk.Config) map[string]gowdk.Addon {
return names
}
-func gowdkGoBlockTarget(pageID string, componentName string, source string, packageName string, block manifest.GoBlock) gowdk.GoBlockTarget {
+func gowdkGoBlockTarget(pageID string, componentName string, sourcePath string, packageName string, block manifest.GoBlock) gowdk.GoBlockTarget {
ownerKind := "layout"
ownerID := ""
if pageID != "" {
@@ -214,23 +215,23 @@ func gowdkGoBlockTarget(pageID string, componentName string, source string, pack
OwnerKind: ownerKind,
OwnerID: ownerID,
OwnerPackage: packageName,
- SourcePath: source,
+ SourcePath: sourcePath,
Body: block.Body,
Span: gowdkSpan(block.Span),
}
}
-func gowdkSpan(span manifest.SourceSpan) gowdk.SourceSpan {
+func gowdkSpan(span source.SourceSpan) gowdk.SourceSpan {
return gowdk.SourceSpan{
Start: gowdk.SourcePosition{Line: span.Start.Line, Column: span.Start.Column},
End: gowdk.SourcePosition{Line: span.End.Line, Column: span.End.Column},
}
}
-func manifestSpan(span gowdk.SourceSpan) manifest.SourceSpan {
- return manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: span.Start.Line, Column: span.Start.Column},
- End: manifest.SourcePosition{Line: span.End.Line, Column: span.End.Column},
+func manifestSpan(span gowdk.SourceSpan) source.SourceSpan {
+ return source.SourceSpan{
+ Start: source.SourcePosition{Line: span.Start.Line, Column: span.Start.Column},
+ End: source.SourcePosition{Line: span.End.Line, Column: span.End.Column},
}
}
diff --git a/internal/compiler/validate_spans.go b/internal/compiler/validate_spans.go
index c3cdda7..db31c75 100644
--- a/internal/compiler/validate_spans.go
+++ b/internal/compiler/validate_spans.go
@@ -2,19 +2,20 @@ package compiler
import (
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"strings"
)
-func firstSpan(spans ...manifest.SourceSpan) manifest.SourceSpan {
+func firstSpan(spans ...source.SourceSpan) source.SourceSpan {
for _, span := range spans {
if hasSpan(span) {
return span
}
}
- return manifest.SourceSpan{}
+ return source.SourceSpan{}
}
-func viewBodyNeedleSpan(component manifest.Component, needle string) manifest.SourceSpan {
+func viewBodyNeedleSpan(component manifest.Component, needle string) source.SourceSpan {
needle = strings.TrimSpace(needle)
if needle == "" || component.Blocks.ViewBody == "" || !hasSpan(component.Blocks.Spans.View) {
return firstSpan(component.Blocks.Spans.View, component.Span)
@@ -32,19 +33,19 @@ func viewBodyNeedleSpan(component manifest.Component, needle string) manifest.So
}
startColumn := len([]rune(before[lineStart:])) + 1
endColumn := startColumn + len([]rune(needle))
- return manifest.SourceSpan{
- Start: manifest.SourcePosition{
+ return source.SourceSpan{
+ Start: source.SourcePosition{
Line: component.Blocks.Spans.View.Start.Line + 1 + lineOffset,
Column: startColumn,
},
- End: manifest.SourcePosition{
+ End: source.SourcePosition{
Line: component.Blocks.Spans.View.Start.Line + 1 + lineOffset,
Column: endColumn,
},
}
}
-func firstNamedSpan(spans []manifest.NamedSpan, fallback manifest.SourceSpan) manifest.SourceSpan {
+func firstNamedSpan(spans []source.NamedSpan, fallback source.SourceSpan) source.SourceSpan {
for _, item := range spans {
if hasSpan(item.Span) {
return item.Span
@@ -53,7 +54,7 @@ func firstNamedSpan(spans []manifest.NamedSpan, fallback manifest.SourceSpan) ma
return fallback
}
-func spanForName(spans []manifest.NamedSpan, name string, fallback manifest.SourceSpan) manifest.SourceSpan {
+func spanForName(spans []source.NamedSpan, name string, fallback source.SourceSpan) source.SourceSpan {
for _, item := range spans {
if item.Name == name && hasSpan(item.Span) {
return item.Span
@@ -62,7 +63,7 @@ func spanForName(spans []manifest.NamedSpan, name string, fallback manifest.Sour
return fallback
}
-func spanForNameOccurrence(spans []manifest.NamedSpan, name string, occurrence int, fallback manifest.SourceSpan) manifest.SourceSpan {
+func spanForNameOccurrence(spans []source.NamedSpan, name string, occurrence int, fallback source.SourceSpan) source.SourceSpan {
if occurrence <= 1 {
return spanForName(spans, name, fallback)
}
@@ -82,6 +83,6 @@ func spanForNameOccurrence(spans []manifest.NamedSpan, name string, occurrence i
return spanForName(spans, name, fallback)
}
-func hasSpan(span manifest.SourceSpan) bool {
+func hasSpan(span source.SourceSpan) bool {
return span.Start.Line > 0 && span.Start.Column > 0 && span.End.Line > 0 && span.End.Column > 0
}
diff --git a/internal/compiler/validate_test.go b/internal/compiler/validate_test.go
index 063e925..963e523 100644
--- a/internal/compiler/validate_test.go
+++ b/internal/compiler/validate_test.go
@@ -13,16 +13,17 @@ import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/addons/ssr"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func TestValidateManifestRejectsMissingPackageDeclaration(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
- if err := os.WriteFile(source, []byte(""), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte(""), 0o644); err != nil {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
ID: "home",
Route: "/",
Blocks: manifest.Blocks{View: true},
@@ -40,8 +41,8 @@ func TestValidateManifestRejectsMissingPackageDeclaration(t *testing.T) {
func TestValidateManifestRejectsPackageMismatchWithSiblingGoFile(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package views\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package views\n"), 0o644); err != nil {
t.Fatal(err)
}
goFile := filepath.Join(root, "handlers.go")
@@ -49,7 +50,7 @@ func TestValidateManifestRejectsPackageMismatchWithSiblingGoFile(t *testing.T) {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
Package: "views",
ID: "home",
Route: "/",
@@ -68,8 +69,8 @@ func TestValidateManifestRejectsPackageMismatchWithSiblingGoFile(t *testing.T) {
func TestValidateManifestAcceptsPackageMatchingSiblingGoFile(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
goFile := filepath.Join(root, "handlers.go")
@@ -77,7 +78,7 @@ func TestValidateManifestAcceptsPackageMatchingSiblingGoFile(t *testing.T) {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
Package: "app",
ID: "home",
Route: "/",
@@ -92,8 +93,8 @@ func TestValidateManifestAcceptsPackageMatchingSiblingGoFile(t *testing.T) {
func TestValidateManifestIgnoresProjectConfigGoPackage(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "styled.page.gwdk")
- if err := os.WriteFile(source, []byte("package css\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "styled.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package css\n"), 0o644); err != nil {
t.Fatal(err)
}
configFile := filepath.Join(root, "gowdk.config.go")
@@ -101,7 +102,7 @@ func TestValidateManifestIgnoresProjectConfigGoPackage(t *testing.T) {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
Package: "css",
ID: "styled",
Route: "/styled",
@@ -116,8 +117,8 @@ func TestValidateManifestIgnoresProjectConfigGoPackage(t *testing.T) {
func TestValidateManifestReportsGoPackageParseErrors(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
goFile := filepath.Join(root, "handlers.go")
@@ -125,7 +126,7 @@ func TestValidateManifestReportsGoPackageParseErrors(t *testing.T) {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
Package: "app",
ID: "home",
Route: "/",
@@ -144,8 +145,8 @@ func TestValidateManifestReportsGoPackageParseErrors(t *testing.T) {
func TestValidateManifestReportsGoPackageTypeErrors(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
goFile := filepath.Join(root, "handlers.go")
@@ -153,7 +154,7 @@ func TestValidateManifestReportsGoPackageTypeErrors(t *testing.T) {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
Package: "app",
ID: "home",
Route: "/",
@@ -182,8 +183,8 @@ func TestValidateManifestReportsGoPackageTypeErrors(t *testing.T) {
func TestValidateManifestTypeChecksDefaultScriptWithSiblingGoFiles(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
goFile := filepath.Join(root, "copy.go")
@@ -196,7 +197,7 @@ type PageCopy struct {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
Package: "app",
ID: "home",
Route: "/",
@@ -218,12 +219,12 @@ type PageCopy struct {
func TestValidateManifestTypeChecksDefaultScriptWithGOWDKImports(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
Package: "app",
ID: "home",
Route: "/",
@@ -246,19 +247,19 @@ func TestValidateManifestTypeChecksDefaultScriptWithGOWDKImports(t *testing.T) {
func TestValidateManifestReportsDefaultScriptTypeErrors(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
Package: "app",
ID: "home",
Route: "/",
Blocks: manifest.Blocks{
View: true,
GoBlocks: []manifest.GoBlock{{
- Span: manifest.SourceSpan{Start: manifest.SourcePosition{Line: 8, Column: 1}},
+ Span: source.SourceSpan{Start: source.SourcePosition{Line: 8, Column: 1}},
Body: `func BrokenCopy() string {
return MissingTitle
}`,
@@ -275,8 +276,8 @@ func TestValidateManifestReportsDefaultScriptTypeErrors(t *testing.T) {
if diagnostic == nil {
t.Fatalf("missing Go package error diagnostic: %#v", diagnostics)
}
- if diagnostic.Source != source {
- t.Fatalf("expected diagnostic source %s, got %#v", source, diagnostic)
+ if diagnostic.Source != sourcePath {
+ t.Fatalf("expected diagnostic source %s, got %#v", sourcePath, diagnostic)
}
if !strings.Contains(diagnostic.Message, "undefined: MissingTitle") {
t.Fatalf("expected undefined inline go block symbol in diagnostic, got %q", diagnostic.Message)
@@ -287,12 +288,12 @@ func TestValidateManifestReportsDefaultScriptTypeErrors(t *testing.T) {
}
func TestGoBlockPackageSourceForValidationPreservesLineDirective(t *testing.T) {
- source := filepath.Join(t.TempDir(), "home.page.gwdk")
+ sourcePath := filepath.Join(t.TempDir(), "home.page.gwdk")
payload, err := goBlockPackageSourceForValidation(packageDeclaration{
- Source: source,
+ Source: sourcePath,
Package: "app",
}, manifest.GoBlock{
- Span: manifest.SourceSpan{Start: manifest.SourcePosition{Line: 8, Column: 1}},
+ Span: source.SourceSpan{Start: source.SourcePosition{Line: 8, Column: 1}},
Body: `func BrokenCopy() string {
return MissingTitle
}`,
@@ -300,11 +301,11 @@ func TestGoBlockPackageSourceForValidationPreservesLineDirective(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- if !strings.Contains(payload, "//line "+filepath.ToSlash(source)+":8") {
+ if !strings.Contains(payload, "//line "+filepath.ToSlash(sourcePath)+":8") {
t.Fatalf("missing line directive in validation source:\n%s", payload)
}
fileSet := token.NewFileSet()
- file, err := parser.ParseFile(fileSet, source, payload, parser.ParseComments|parser.AllErrors)
+ file, err := parser.ParseFile(fileSet, sourcePath, payload, parser.ParseComments|parser.AllErrors)
if err != nil {
t.Fatal(err)
}
@@ -317,12 +318,12 @@ func TestGoBlockPackageSourceForValidationPreservesLineDirective(t *testing.T) {
func TestValidateManifestTypeChecksDefaultGoBlockAsStaticPackageGo(t *testing.T) {
root := t.TempDir()
- source := filepath.Join(root, "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(root, "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
page := manifest.Page{
- Source: source,
+ Source: sourcePath,
Package: "app",
ID: "home",
Route: "/",
@@ -633,14 +634,14 @@ func TestValidatePageRejectsSSRWithoutAddon(t *testing.T) {
}
func TestValidatePageRequiresExplicitGuard(t *testing.T) {
- source := filepath.Join(t.TempDir(), "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(t.TempDir(), "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
page := manifest.Page{
ID: "home",
Route: "/",
- Source: source,
+ Source: sourcePath,
Blocks: manifest.Blocks{View: true},
}
@@ -651,14 +652,14 @@ func TestValidatePageRequiresExplicitGuard(t *testing.T) {
}
func TestValidatePageRequiresPublicGuardToBeExclusive(t *testing.T) {
- source := filepath.Join(t.TempDir(), "home.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(t.TempDir(), "home.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
page := manifest.Page{
ID: "home",
Route: "/",
- Source: source,
+ Source: sourcePath,
Guard: []string{"public", "auth.required"},
Blocks: manifest.Blocks{View: true},
}
@@ -670,14 +671,14 @@ func TestValidatePageRequiresPublicGuardToBeExclusive(t *testing.T) {
}
func TestValidatePageRejectsProtectedGuardOnBuildTimePage(t *testing.T) {
- source := filepath.Join(t.TempDir(), "dashboard.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(t.TempDir(), "dashboard.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
page := manifest.Page{
ID: "dashboard",
Route: "/dashboard",
- Source: source,
+ Source: sourcePath,
Guard: []string{"auth.required"},
Render: gowdk.SPA,
Blocks: manifest.Blocks{View: true},
@@ -690,14 +691,14 @@ func TestValidatePageRejectsProtectedGuardOnBuildTimePage(t *testing.T) {
}
func TestValidatePageAllowsProtectedGuardOnRequestTimePage(t *testing.T) {
- source := filepath.Join(t.TempDir(), "dashboard.page.gwdk")
- if err := os.WriteFile(source, []byte("package app\n"), 0o644); err != nil {
+ sourcePath := filepath.Join(t.TempDir(), "dashboard.page.gwdk")
+ if err := os.WriteFile(sourcePath, []byte("package app\n"), 0o644); err != nil {
t.Fatal(err)
}
page := manifest.Page{
ID: "dashboard",
Route: "/dashboard",
- Source: source,
+ Source: sourcePath,
Guard: []string{"auth.required"},
Render: gowdk.SSR,
Blocks: manifest.Blocks{View: true},
@@ -793,11 +794,11 @@ func TestValidateManifestRejectsDuplicatePageStore(t *testing.T) {
Stores: []manifest.Store{
{
Name: "cart",
- Span: manifest.SourceSpan{Start: manifest.SourcePosition{Line: 5, Column: 1}, End: manifest.SourcePosition{Line: 5, Column: 40}},
+ Span: source.SourceSpan{Start: source.SourcePosition{Line: 5, Column: 1}, End: source.SourcePosition{Line: 5, Column: 40}},
},
{
Name: "cart",
- Span: manifest.SourceSpan{Start: manifest.SourcePosition{Line: 6, Column: 1}, End: manifest.SourcePosition{Line: 6, Column: 40}},
+ Span: source.SourceSpan{Start: source.SourcePosition{Line: 6, Column: 1}, End: source.SourcePosition{Line: 6, Column: 40}},
},
},
Blocks: manifest.Blocks{View: true, ViewBody: `Cart`},
@@ -828,7 +829,7 @@ func TestValidateManifestRejectsUnknownComponentStoreUse(t *testing.T) {
Client: true,
ClientBody: "use cart",
Spans: manifest.BlockSpans{
- Client: manifest.SourceSpan{Start: manifest.SourcePosition{Line: 4, Column: 1}, End: manifest.SourcePosition{Line: 4, Column: 9}},
+ Client: source.SourceSpan{Start: source.SourcePosition{Line: 4, Column: 1}, End: source.SourcePosition{Line: 4, Column: 9}},
},
View: true,
ViewBody: ``,
@@ -1372,16 +1373,16 @@ func TestValidateManifestRejectsDuplicateComponentEmitNames(t *testing.T) {
Emits: []manifest.Emit{
{
Name: "select",
- Span: manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: 4, Column: 3},
- End: manifest.SourcePosition{Line: 4, Column: 16},
+ Span: source.SourceSpan{
+ Start: source.SourcePosition{Line: 4, Column: 3},
+ End: source.SourcePosition{Line: 4, Column: 16},
},
},
{
Name: "select",
- Span: manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: 5, Column: 3},
- End: manifest.SourcePosition{Line: 5, Column: 20},
+ Span: source.SourceSpan{
+ Start: source.SourcePosition{Line: 5, Column: 3},
+ End: source.SourcePosition{Line: 5, Column: 20},
},
},
},
@@ -1438,9 +1439,9 @@ func TestValidateManifestClientParseErrorPointsToClientLine(t *testing.T) {
Client: true,
ClientBody: "fn Bad() {\n if Count {\n }\n}",
Spans: manifest.BlockSpans{
- Client: manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: 10, Column: 1},
- End: manifest.SourcePosition{Line: 14, Column: 1},
+ Client: source.SourceSpan{
+ Start: source.SourcePosition{Line: 10, Column: 1},
+ End: source.SourcePosition{Line: 14, Column: 1},
},
},
View: true,
@@ -2383,7 +2384,7 @@ func TestValidateManifestViewEventDiagnosticPointsToExpression(t *testing.T) {
View: true,
ViewBody: ``,
Spans: manifest.BlockSpans{
- View: manifest.SourceSpan{Start: manifest.SourcePosition{Line: 9, Column: 1}, End: manifest.SourcePosition{Line: 9, Column: 7}},
+ View: source.SourceSpan{Start: source.SourcePosition{Line: 9, Column: 1}, End: source.SourcePosition{Line: 9, Column: 7}},
},
},
}}}
@@ -2407,7 +2408,7 @@ func TestValidateManifestUnknownViewFieldDiagnosticPointsToIdentifier(t *testing
View: true,
ViewBody: ``,
Spans: manifest.BlockSpans{
- View: manifest.SourceSpan{Start: manifest.SourcePosition{Line: 4, Column: 1}, End: manifest.SourcePosition{Line: 4, Column: 7}},
+ View: source.SourceSpan{Start: source.SourcePosition{Line: 4, Column: 1}, End: source.SourcePosition{Line: 4, Column: 7}},
},
},
}}}
@@ -2431,7 +2432,7 @@ func TestValidateManifestBadGForDiagnosticPointsToDirectiveValue(t *testing.T) {
View: true,
ViewBody: ``,
Spans: manifest.BlockSpans{
- View: manifest.SourceSpan{Start: manifest.SourcePosition{Line: 12, Column: 1}, End: manifest.SourcePosition{Line: 12, Column: 7}},
+ View: source.SourceSpan{Start: source.SourcePosition{Line: 12, Column: 1}, End: source.SourcePosition{Line: 12, Column: 7}},
},
},
}}}
@@ -3431,7 +3432,7 @@ func TestValidatePageRejectsDuplicateRouteParams(t *testing.T) {
Blocks: manifest.Blocks{View: true},
Spans: manifest.PageSpans{
Route: testSourceSpan(3, 8, 3, 28),
- RouteParams: []manifest.NamedSpan{
+ RouteParams: []source.NamedSpan{
{Name: "slug", Span: testSourceSpan(3, 14, 3, 20)},
{Name: "slug", Span: testSourceSpan(3, 21, 3, 27)},
},
@@ -3461,7 +3462,7 @@ func TestValidatePageRouteDiagnosticsUseExactSpans(t *testing.T) {
Route: "/save/{id:uuid}",
Span: testSourceSpan(6, 1, 6, 32),
RouteSpan: testSourceSpan(6, 17, 6, 34),
- RouteParams: []manifest.NamedSpan{{Name: "id", Span: testSourceSpan(6, 24, 6, 33)}},
+ RouteParams: []source.NamedSpan{{Name: "id", Span: testSourceSpan(6, 24, 6, 33)}},
}}
diagnostics := ValidatePage(gowdk.Config{}, page)
@@ -3480,7 +3481,7 @@ func TestValidatePageRouteDiagnosticsUseExactSpans(t *testing.T) {
Route: "/api/{id:uuid}",
Span: testSourceSpan(9, 1, 9, 31),
RouteSpan: testSourceSpan(9, 16, 9, 31),
- RouteParams: []manifest.NamedSpan{{Name: "id", Span: testSourceSpan(9, 21, 9, 30)}},
+ RouteParams: []source.NamedSpan{{Name: "id", Span: testSourceSpan(9, 21, 9, 30)}},
}}
diagnostics := ValidatePage(gowdk.Config{}, page)
@@ -3499,7 +3500,7 @@ func TestValidatePageRouteDiagnosticsUseExactSpans(t *testing.T) {
Route: "/preview/{id}",
Span: testSourceSpan(12, 1, 12, 34),
RouteSpan: testSourceSpan(12, 20, 12, 35),
- RouteParams: []manifest.NamedSpan{{Name: "id", Span: testSourceSpan(12, 29, 12, 33)}},
+ RouteParams: []source.NamedSpan{{Name: "id", Span: testSourceSpan(12, 29, 12, 33)}},
}}
diagnostics := ValidatePage(gowdk.Config{}, page)
@@ -3904,17 +3905,17 @@ func firstDiagnostic(diagnostics []ValidationError, code string) *ValidationErro
return nil
}
-func assertSourceSpan(t *testing.T, span manifest.SourceSpan, startLine, startColumn, endLine, endColumn int) {
+func assertSourceSpan(t *testing.T, span source.SourceSpan, startLine, startColumn, endLine, endColumn int) {
t.Helper()
if span.Start.Line != startLine || span.Start.Column != startColumn || span.End.Line != endLine || span.End.Column != endColumn {
t.Fatalf("unexpected source span: got %#v, want %d:%d-%d:%d", span, startLine, startColumn, endLine, endColumn)
}
}
-func testSourceSpan(startLine, startColumn, endLine, endColumn int) manifest.SourceSpan {
- return manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: startLine, Column: startColumn},
- End: manifest.SourcePosition{Line: endLine, Column: endColumn},
+func testSourceSpan(startLine, startColumn, endLine, endColumn int) source.SourceSpan {
+ return source.SourceSpan{
+ Start: source.SourcePosition{Line: startLine, Column: startColumn},
+ End: source.SourcePosition{Line: endLine, Column: endColumn},
}
}
diff --git a/internal/contractscan/input_structs.go b/internal/contractscan/input_structs.go
index 601d394..92029fc 100644
--- a/internal/contractscan/input_structs.go
+++ b/internal/contractscan/input_structs.go
@@ -6,7 +6,7 @@ import (
"strconv"
"strings"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func contractInputStruct(typeName string, structType *ast.StructType) inputStruct {
@@ -14,7 +14,7 @@ func contractInputStruct(typeName string, structType *ast.StructType) inputStruc
return inputStruct{}
}
seen := map[string]bool{}
- var fields []manifest.BackendInputField
+ var fields []source.BackendInputField
for _, field := range structType.Fields.List {
if len(field.Names) == 0 {
return inputStruct{Message: fmt.Sprintf("contract input %s cannot use embedded fields", typeName)}
@@ -48,7 +48,7 @@ func contractInputStruct(typeName string, structType *ast.StructType) inputStruc
return inputStruct{Message: fmt.Sprintf("contract input %s maps multiple fields to form field %q", typeName, nameFormName)}
}
seen[nameFormName] = true
- fields = append(fields, manifest.BackendInputField{
+ fields = append(fields, source.BackendInputField{
FieldName: name.Name,
FormName: nameFormName,
Type: fieldType,
diff --git a/internal/contractscan/packages.go b/internal/contractscan/packages.go
index 99329d9..5a25db9 100644
--- a/internal/contractscan/packages.go
+++ b/internal/contractscan/packages.go
@@ -11,7 +11,7 @@ import (
"strconv"
"strings"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
runtimecontracts "github.com/cssbruno/gowdk/runtime/contracts"
)
@@ -22,7 +22,7 @@ type fileScan struct {
}
type inputStruct struct {
- Fields []manifest.BackendInputField
+ Fields []source.BackendInputField
Message string
}
@@ -63,11 +63,11 @@ func parseScanPackages(fset *token.FileSet, root string, files []string) ([][]pa
}
func parseScanFile(fset *token.FileSet, root string, path string) (parsedGoFile, error) {
- source, err := os.ReadFile(path)
+ src, err := os.ReadFile(path)
if err != nil {
return parsedGoFile{}, err
}
- file, err := parser.ParseFile(fset, path, source, 0)
+ file, err := parser.ParseFile(fset, path, src, 0)
if err != nil {
return parsedGoFile{}, err
}
@@ -183,7 +183,7 @@ func applyContractInputFields(contracts []Contract, structs map[string]inputStru
if !ok || inputStruct.Message != "" {
continue
}
- contracts[index].InputFields = append([]manifest.BackendInputField(nil), inputStruct.Fields...)
+ contracts[index].InputFields = append([]source.BackendInputField(nil), inputStruct.Fields...)
}
}
diff --git a/internal/contractscan/references.go b/internal/contractscan/references.go
index 2584ea6..38e8dea 100644
--- a/internal/contractscan/references.go
+++ b/internal/contractscan/references.go
@@ -5,7 +5,7 @@ import (
"strings"
"github.com/cssbruno/gowdk/internal/gwdkir"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
runtimecontracts "github.com/cssbruno/gowdk/runtime/contracts"
)
@@ -62,7 +62,7 @@ func LinkReferences(refs []gwdkir.ContractReference, report Report) []gwdkir.Con
}
linked[index].Result = contract.Result
linked[index].Roles = append([]string(nil), contract.Roles...)
- linked[index].InputFields = append([]manifest.BackendInputField(nil), contract.InputFields...)
+ linked[index].InputFields = append([]source.BackendInputField(nil), contract.InputFields...)
if diagnostic, bad := lookupContractDiagnostic(invalid[ref.Kind], ref); bad {
linked[index].Status = gwdkir.ContractBindingInvalid
linked[index].Message = diagnostic.Message
diff --git a/internal/contractscan/scan.go b/internal/contractscan/scan.go
index 6073ef6..9fb05e5 100644
--- a/internal/contractscan/scan.go
+++ b/internal/contractscan/scan.go
@@ -10,7 +10,7 @@ import (
"sort"
"strings"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
runtimecontracts "github.com/cssbruno/gowdk/runtime/contracts"
)
@@ -28,7 +28,7 @@ type Contract struct {
ResultImportPath string `json:"resultImportPath,omitempty"`
Handler string `json:"handler,omitempty"`
Register string `json:"register,omitempty"`
- InputFields []manifest.BackendInputField `json:"inputFields,omitempty"`
+ InputFields []source.BackendInputField `json:"inputFields,omitempty"`
Emits []EventRef `json:"emits,omitempty"`
Roles []string `json:"roles,omitempty"`
Source string `json:"source"`
diff --git a/internal/contractscan/scan_test.go b/internal/contractscan/scan_test.go
index dc678d5..e2fe71b 100644
--- a/internal/contractscan/scan_test.go
+++ b/internal/contractscan/scan_test.go
@@ -8,7 +8,7 @@ import (
"testing"
"github.com/cssbruno/gowdk/internal/gwdkir"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
runtimecontracts "github.com/cssbruno/gowdk/runtime/contracts"
)
@@ -766,7 +766,7 @@ func findDiagnostic(t *testing.T, diagnostics []Diagnostic, code string) Diagnos
return Diagnostic{}
}
-func inputFieldsString(fields []manifest.BackendInputField) string {
+func inputFieldsString(fields []source.BackendInputField) string {
parts := make([]string, 0, len(fields))
for _, field := range fields {
parts = append(parts, field.FieldName+":"+field.FormName+":"+field.Type)
diff --git a/internal/gwdkanalysis/analyzer_test.go b/internal/gwdkanalysis/analyzer_test.go
index 1030717..d31419e 100644
--- a/internal/gwdkanalysis/analyzer_test.go
+++ b/internal/gwdkanalysis/analyzer_test.go
@@ -8,6 +8,7 @@ import (
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
"github.com/cssbruno/gowdk/internal/parser"
+ "github.com/cssbruno/gowdk/internal/source"
)
func TestAnalyzeLowersASTIntoManifestAndIR(t *testing.T) {
@@ -217,7 +218,7 @@ view {
}
func TestAnalyzeAddsCommandReferencesToIR(t *testing.T) {
- source := `package pages
+ src := `package pages
@page patients
@route "/patients"
@guard auth.required
@@ -230,7 +231,7 @@ view {
}
`
- pageAST := mustParse(t, source)
+ pageAST := mustParse(t, src)
result, err := Analyze(gowdk.Config{}, []SourceFile{{Path: "pages/patients.page.gwdk", Kind: SourcePage, AST: pageAST}})
if err != nil {
t.Fatal(err)
@@ -251,14 +252,14 @@ view {
if ref.ImportAlias != "patients" || ref.ImportPath != "example.com/app/patients" || ref.Type != "CreatePatient" {
t.Fatalf("unexpected command import/type metadata: %#v", ref)
}
- wantColumn := strings.Index(sourceLine(source, 9), "g:command") + 1
+ wantColumn := strings.Index(sourceLine(src, 9), "g:command") + 1
if ref.Span.Start.Line != 9 || ref.Span.Start.Column != wantColumn {
t.Fatalf("expected g:command span at 9:%d, got %#v", wantColumn, ref.Span)
}
}
func TestAnalyzeAddsQueryReferencesToIR(t *testing.T) {
- source := `package pages
+ src := `package pages
@page patients
@route "/patients"
@@ -270,7 +271,7 @@ view {
}
`
- pageAST := mustParse(t, source)
+ pageAST := mustParse(t, src)
result, err := Analyze(gowdk.Config{}, []SourceFile{{Path: "pages/patients.page.gwdk", Kind: SourcePage, AST: pageAST}})
if err != nil {
t.Fatal(err)
@@ -288,7 +289,7 @@ view {
if ref.ImportAlias != "patients" || ref.ImportPath != "example.com/app/patients" || ref.Type != "GetPatientPage" {
t.Fatalf("unexpected query import/type metadata: %#v", ref)
}
- wantColumn := strings.Index(sourceLine(source, 8), "g:query") + 1
+ wantColumn := strings.Index(sourceLine(src, 8), "g:query") + 1
if ref.Span.Start.Line != 8 || ref.Span.Start.Column != wantColumn {
t.Fatalf("expected g:query span at 8:%d, got %#v", wantColumn, ref.Span)
}
@@ -310,11 +311,11 @@ func TestBuildIRAttachesBackendBindings(t *testing.T) {
BlockName: "Subscribe",
Method: "POST",
Route: "/newsletter",
- Status: manifest.BackendBindingBound,
+ Status: source.BackendBindingBound,
ImportPath: "example.com/app/newsletter",
PackageName: "newsletter",
FunctionName: "Subscribe",
- Signature: manifest.BackendSignatureAction0,
+ Signature: source.BackendSignatureAction0,
}},
})
@@ -322,7 +323,7 @@ func TestBuildIRAttachesBackendBindings(t *testing.T) {
t.Fatalf("expected one endpoint, got %#v", ir.Endpoints)
}
binding := ir.Endpoints[0].Binding
- if binding.Status != manifest.BackendBindingBound || binding.FunctionName != "Subscribe" {
+ if binding.Status != source.BackendBindingBound || binding.FunctionName != "Subscribe" {
t.Fatalf("expected backend binding on IR endpoint, got %#v", binding)
}
}
@@ -344,18 +345,18 @@ func TestBuildIRIncludesStandaloneGoEndpointSource(t *testing.T) {
BlockName: "Session",
Method: "GET",
Route: "/api/session",
- Status: manifest.BackendBindingBound,
+ Status: source.BackendBindingBound,
ImportPath: "example.com/app/api",
PackageName: "api",
FunctionName: "Session",
- Signature: manifest.BackendSignatureAPI,
+ Signature: source.BackendSignatureAPI,
}},
})
if len(ir.Endpoints) != 1 {
t.Fatalf("expected one endpoint, got %#v", ir.Endpoints)
}
endpoint := ir.Endpoints[0]
- if endpoint.Source != gwdkir.EndpointSourceGo || endpoint.PageID != "api.Session" || endpoint.Binding.Signature != manifest.BackendSignatureAPI {
+ if endpoint.Source != gwdkir.EndpointSourceGo || endpoint.PageID != "api.Session" || endpoint.Binding.Signature != source.BackendSignatureAPI {
t.Fatalf("unexpected endpoint IR: %#v", endpoint)
}
}
@@ -423,17 +424,17 @@ view {
}
}
-func mustParse(t *testing.T, source string) parser.SyntaxFile {
+func mustParse(t *testing.T, src string) parser.SyntaxFile {
t.Helper()
- file, err := parser.ParseSyntax([]byte(source))
+ file, err := parser.ParseSyntax([]byte(src))
if err != nil {
t.Fatal(err)
}
return file
}
-func sourceLine(source string, line int) string {
- lines := strings.Split(source, "\n")
+func sourceLine(src string, line int) string {
+ lines := strings.Split(src, "\n")
if line <= 0 || line > len(lines) {
return ""
}
diff --git a/internal/gwdkanalysis/ir_bindings.go b/internal/gwdkanalysis/ir_bindings.go
index bdbf1c0..1543cc7 100644
--- a/internal/gwdkanalysis/ir_bindings.go
+++ b/internal/gwdkanalysis/ir_bindings.go
@@ -5,6 +5,7 @@ import (
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func attachBackendBindings(program *gwdkir.Program, bindings []manifest.BackendBinding) {
@@ -35,7 +36,7 @@ func attachBackendBindings(program *gwdkir.Program, bindings []manifest.BackendB
Signature: binding.Signature,
InputType: binding.InputType,
InputPointer: binding.InputPointer,
- InputFields: append([]manifest.BackendInputField(nil), binding.InputFields...),
+ InputFields: append([]source.BackendInputField(nil), binding.InputFields...),
}
}
for index := range program.Pages {
diff --git a/internal/gwdkanalysis/ir_builder.go b/internal/gwdkanalysis/ir_builder.go
index 2a2cdc8..97a32d3 100644
--- a/internal/gwdkanalysis/ir_builder.go
+++ b/internal/gwdkanalysis/ir_builder.go
@@ -8,6 +8,7 @@ import (
"github.com/cssbruno/gowdk/internal/cssscope"
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
// BuildIR converts a normalized manifest into the stable compiler IR.
@@ -43,14 +44,14 @@ type irBuilder struct {
packages map[string]*gwdkir.Package
}
-func (builder *irBuilder) ensurePackage(name string, source string) *gwdkir.Package {
+func (builder *irBuilder) ensurePackage(name string, src string) *gwdkir.Package {
pkg := builder.packages[name]
if pkg == nil {
pkg = &gwdkir.Package{Name: name}
builder.packages[name] = pkg
}
- dir := filepath.Dir(source)
- if source != "" && !contains(pkg.SourceDirs, dir) {
+ dir := filepath.Dir(src)
+ if src != "" && !contains(pkg.SourceDirs, dir) {
pkg.SourceDirs = append(pkg.SourceDirs, dir)
}
return pkg
@@ -132,7 +133,7 @@ func (builder *irBuilder) addPageAssets(page manifest.Page) {
for index, script := range page.InlineJS {
name := script.Name
if name == "" {
- name = manifest.InlineScriptName(index)
+ name = source.InlineScriptName(index)
}
builder.program.Assets = append(builder.program.Assets, gwdkir.Asset{
Kind: gwdkir.AssetJS,
@@ -262,7 +263,7 @@ func (builder *irBuilder) addComponentAssets(component manifest.Component) {
for index, script := range component.InlineJS {
name := script.Name
if name == "" {
- name = manifest.InlineScriptName(index)
+ name = source.InlineScriptName(index)
}
builder.program.Assets = append(builder.program.Assets, gwdkir.Asset{
Kind: gwdkir.AssetJS,
diff --git a/internal/gwdkanalysis/ir_contracts.go b/internal/gwdkanalysis/ir_contracts.go
index 016ef2d..29999d6 100644
--- a/internal/gwdkanalysis/ir_contracts.go
+++ b/internal/gwdkanalysis/ir_contracts.go
@@ -4,7 +4,7 @@ import (
"strings"
"github.com/cssbruno/gowdk/internal/gwdkir"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/internal/view"
)
@@ -69,7 +69,7 @@ func irContractReferenceKind(kind view.ContractReferenceKind) gwdkir.ContractKin
}
}
-func templateOffsetSpan(template gwdkir.Template, start int, end int) manifest.SourceSpan {
+func templateOffsetSpan(template gwdkir.Template, start int, end int) source.SourceSpan {
if start < 0 || end <= start || start >= len([]rune(template.Body)) {
return template.Span
}
@@ -78,10 +78,10 @@ func templateOffsetSpan(template gwdkir.Template, start int, end int) manifest.S
if startPos.Line == 0 || endPos.Line == 0 {
return template.Span
}
- return manifest.SourceSpan{Start: startPos, End: endPos}
+ return source.SourceSpan{Start: startPos, End: endPos}
}
-func templateOffsetPosition(template gwdkir.Template, offset int) manifest.SourcePosition {
+func templateOffsetPosition(template gwdkir.Template, offset int) source.SourcePosition {
line := template.BodyStart.Line
column := template.BodyStart.Column
if line == 0 {
@@ -90,7 +90,7 @@ func templateOffsetPosition(template gwdkir.Template, offset int) manifest.Sourc
}
for index, char := range []rune(template.Body) {
if index == offset {
- return manifest.SourcePosition{Line: line, Column: column}
+ return source.SourcePosition{Line: line, Column: column}
}
if char == '\n' {
line++
@@ -100,7 +100,7 @@ func templateOffsetPosition(template gwdkir.Template, offset int) manifest.Sourc
column++
}
if offset == len([]rune(template.Body)) {
- return manifest.SourcePosition{Line: line, Column: column}
+ return source.SourcePosition{Line: line, Column: column}
}
- return manifest.SourcePosition{}
+ return source.SourcePosition{}
}
diff --git a/internal/gwdkanalysis/ir_lowering.go b/internal/gwdkanalysis/ir_lowering.go
index 2c9501f..f61eca5 100644
--- a/internal/gwdkanalysis/ir_lowering.go
+++ b/internal/gwdkanalysis/ir_lowering.go
@@ -4,6 +4,7 @@ import (
"github.com/cssbruno/gowdk"
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func routeKind(mode gowdk.RenderMode, page manifest.Page) gwdkir.RouteKind {
@@ -50,21 +51,21 @@ func lowerIRPage(page manifest.Page) gwdkir.Page {
Description: page.Spans.Description,
Canonical: page.Spans.Canonical,
Image: page.Spans.Image,
- Layouts: append([]manifest.NamedSpan(nil), page.Spans.Layouts...),
- Guard: append([]manifest.NamedSpan(nil), page.Spans.Guard...),
- CSS: append([]manifest.NamedSpan(nil), page.Spans.CSS...),
- JS: append([]manifest.NamedSpan(nil), page.Spans.JS...),
- InlineJS: append([]manifest.NamedSpan(nil), page.Spans.InlineJS...),
- RouteParams: append([]manifest.NamedSpan(nil), page.Spans.RouteParams...),
+ Layouts: append([]source.NamedSpan(nil), page.Spans.Layouts...),
+ Guard: append([]source.NamedSpan(nil), page.Spans.Guard...),
+ CSS: append([]source.NamedSpan(nil), page.Spans.CSS...),
+ JS: append([]source.NamedSpan(nil), page.Spans.JS...),
+ InlineJS: append([]source.NamedSpan(nil), page.Spans.InlineJS...),
+ RouteParams: append([]source.NamedSpan(nil), page.Spans.RouteParams...),
},
}
}
-func copyRouteParams(params []manifest.RouteParam) []manifest.RouteParam {
+func copyRouteParams(params []source.RouteParam) []source.RouteParam {
if len(params) == 0 {
return nil
}
- out := make([]manifest.RouteParam, len(params))
+ out := make([]source.RouteParam, len(params))
copy(out, params)
return out
}
@@ -90,19 +91,19 @@ func lowerIRComponent(component manifest.Component) gwdkir.Component {
Span: component.Span,
PackageSpan: component.PackageSpan,
Spans: gwdkir.ComponentSpans{
- CSS: append([]manifest.NamedSpan(nil), component.Spans.CSS...),
- JS: append([]manifest.NamedSpan(nil), component.Spans.JS...),
- InlineJS: append([]manifest.NamedSpan(nil), component.Spans.InlineJS...),
- Assets: append([]manifest.NamedSpan(nil), component.Spans.Assets...),
+ CSS: append([]source.NamedSpan(nil), component.Spans.CSS...),
+ JS: append([]source.NamedSpan(nil), component.Spans.JS...),
+ InlineJS: append([]source.NamedSpan(nil), component.Spans.InlineJS...),
+ Assets: append([]source.NamedSpan(nil), component.Spans.Assets...),
},
}
}
-func copyInlineScripts(scripts []manifest.InlineScript) []manifest.InlineScript {
+func copyInlineScripts(scripts []source.InlineScript) []source.InlineScript {
if len(scripts) == 0 {
return nil
}
- out := make([]manifest.InlineScript, len(scripts))
+ out := make([]source.InlineScript, len(scripts))
copy(out, scripts)
return out
}
@@ -141,12 +142,12 @@ func lowerIRBlocks(blocks manifest.Blocks) gwdkir.Blocks {
Build: blocks.Spans.Build,
Load: blocks.Spans.Load,
Client: blocks.Spans.Client,
- GoBlocks: append([]manifest.NamedSpan(nil), blocks.Spans.GoBlocks...),
+ GoBlocks: append([]source.NamedSpan(nil), blocks.Spans.GoBlocks...),
View: blocks.Spans.View,
ViewBodyStart: blocks.Spans.ViewBodyStart,
- Actions: append([]manifest.NamedSpan(nil), blocks.Spans.Actions...),
- APIs: append([]manifest.NamedSpan(nil), blocks.Spans.APIs...),
- Fragments: append([]manifest.NamedSpan(nil), blocks.Spans.Fragments...),
+ Actions: append([]source.NamedSpan(nil), blocks.Spans.Actions...),
+ APIs: append([]source.NamedSpan(nil), blocks.Spans.APIs...),
+ Fragments: append([]source.NamedSpan(nil), blocks.Spans.Fragments...),
Exports: blocks.Spans.Exports,
Emits: blocks.Spans.Emits,
},
@@ -169,7 +170,7 @@ func lowerIRActions(actions []manifest.Action) []gwdkir.Action {
ErrorPage: action.ErrorPage,
Span: action.Span,
RouteSpan: action.RouteSpan,
- RouteParams: append([]manifest.NamedSpan(nil), action.RouteParams...),
+ RouteParams: append([]source.NamedSpan(nil), action.RouteParams...),
InputSpan: action.InputSpan,
ValidationSpan: action.ValidationSpan,
RedirectSpan: action.RedirectSpan,
@@ -189,7 +190,7 @@ func lowerIRAPIs(apis []manifest.API) []gwdkir.API {
ErrorPage: api.ErrorPage,
Span: api.Span,
RouteSpan: api.RouteSpan,
- RouteParams: append([]manifest.NamedSpan(nil), api.RouteParams...),
+ RouteParams: append([]source.NamedSpan(nil), api.RouteParams...),
ErrorPageSpan: api.ErrorPageSpan,
})
}
@@ -228,7 +229,7 @@ func lowerIRFragmentEndpoints(fragments []manifest.FragmentEndpoint) []gwdkir.Fr
Span: fragment.Span,
RouteSpan: fragment.RouteSpan,
TargetSpan: fragment.TargetSpan,
- RouteParams: append([]manifest.NamedSpan(nil), fragment.RouteParams...),
+ RouteParams: append([]source.NamedSpan(nil), fragment.RouteParams...),
})
}
return out
diff --git a/internal/gwdkanalysis/manifest_lowering.go b/internal/gwdkanalysis/manifest_lowering.go
index d4b4980..d555f95 100644
--- a/internal/gwdkanalysis/manifest_lowering.go
+++ b/internal/gwdkanalysis/manifest_lowering.go
@@ -7,11 +7,12 @@ import (
"github.com/cssbruno/gowdk/internal/gwdkast"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
// LowerPage lowers one page AST into manifest compatibility records.
-func LowerPage(source string, ast gwdkast.File) (manifest.Page, error) {
- page := manifest.Page{Source: source}
+func LowerPage(src string, ast gwdkast.File) (manifest.Page, error) {
+ page := manifest.Page{Source: src}
if ast.Package != nil {
page.Package = ast.Package.Name
page.Spans.Package = ast.Package.Span
@@ -43,25 +44,25 @@ func LowerPage(source string, ast gwdkast.File) (manifest.Page, error) {
}
for _, layout := range ast.Layouts {
page.Layouts = append(page.Layouts, layout.ID)
- page.Spans.Layouts = append(page.Spans.Layouts, manifest.NamedSpan{Name: layout.ID, Span: layout.Span})
+ page.Spans.Layouts = append(page.Spans.Layouts, source.NamedSpan{Name: layout.ID, Span: layout.Span})
}
for _, guard := range ast.Guards {
page.Guard = append(page.Guard, guard.Name)
- page.Spans.Guard = append(page.Spans.Guard, manifest.NamedSpan{Name: guard.Name, Span: guard.Span})
+ page.Spans.Guard = append(page.Spans.Guard, source.NamedSpan{Name: guard.Name, Span: guard.Span})
}
for _, asset := range ast.CSS {
page.CSS = append(page.CSS, asset.Path)
- page.Spans.CSS = append(page.Spans.CSS, manifest.NamedSpan{Name: asset.Path, Span: asset.Span})
+ page.Spans.CSS = append(page.Spans.CSS, source.NamedSpan{Name: asset.Path, Span: asset.Span})
}
for _, asset := range ast.JS {
if strings.TrimSpace(asset.Path) != "" {
page.JS = append(page.JS, asset.Path)
- page.Spans.JS = append(page.Spans.JS, manifest.NamedSpan{Name: asset.Path, Span: asset.Span})
+ page.Spans.JS = append(page.Spans.JS, source.NamedSpan{Name: asset.Path, Span: asset.Span})
continue
}
- name := manifest.InlineScriptName(len(page.InlineJS))
- page.InlineJS = append(page.InlineJS, manifest.InlineScript{Name: name, Body: asset.Inline, Span: asset.Span})
- page.Spans.InlineJS = append(page.Spans.InlineJS, manifest.NamedSpan{Name: name, Span: asset.Span})
+ name := source.InlineScriptName(len(page.InlineJS))
+ page.InlineJS = append(page.InlineJS, source.InlineScript{Name: name, Body: asset.Inline, Span: asset.Span})
+ page.Spans.InlineJS = append(page.Spans.InlineJS, source.NamedSpan{Name: name, Span: asset.Span})
}
for _, annotation := range ast.Annotations {
@@ -86,7 +87,7 @@ func LowerPage(source string, ast gwdkast.File) (manifest.Page, error) {
RouteParams: routeParamSpans(endpoint.Route, endpoint.Span),
ErrorPageSpan: endpoint.ErrorPageSpan,
})
- page.Blocks.Spans.Actions = append(page.Blocks.Spans.Actions, manifest.NamedSpan{Name: endpoint.Name, Span: endpoint.Span})
+ page.Blocks.Spans.Actions = append(page.Blocks.Spans.Actions, source.NamedSpan{Name: endpoint.Name, Span: endpoint.Span})
}
for _, endpoint := range ast.APIs {
page.Blocks.APIs = append(page.Blocks.APIs, manifest.API{
@@ -99,7 +100,7 @@ func LowerPage(source string, ast gwdkast.File) (manifest.Page, error) {
RouteParams: routeParamSpans(endpoint.Route, endpoint.Span),
ErrorPageSpan: endpoint.ErrorPageSpan,
})
- page.Blocks.Spans.APIs = append(page.Blocks.Spans.APIs, manifest.NamedSpan{Name: endpoint.Name, Span: endpoint.Span})
+ page.Blocks.Spans.APIs = append(page.Blocks.Spans.APIs, source.NamedSpan{Name: endpoint.Name, Span: endpoint.Span})
}
for _, fragment := range ast.Fragments {
page.Blocks.Fragments = append(page.Blocks.Fragments, manifest.FragmentEndpoint{
@@ -113,25 +114,25 @@ func LowerPage(source string, ast gwdkast.File) (manifest.Page, error) {
TargetSpan: fragment.TargetSpan,
RouteParams: routeParamSpans(fragment.Route, fragment.RouteSpan),
})
- page.Blocks.Spans.Fragments = append(page.Blocks.Spans.Fragments, manifest.NamedSpan{Name: fragment.Name, Span: fragment.Span})
+ page.Blocks.Spans.Fragments = append(page.Blocks.Spans.Fragments, source.NamedSpan{Name: fragment.Name, Span: fragment.Span})
}
if page.ID == "" {
- page.ID = derivedPageID(source)
+ page.ID = derivedPageID(src)
}
if page.ID == "" {
- return manifest.Page{}, fmt.Errorf("%s: missing @page", source)
+ return manifest.Page{}, fmt.Errorf("%s: missing @page", src)
}
if page.Route == "" {
- return manifest.Page{}, fmt.Errorf("%s: missing @route", source)
+ return manifest.Page{}, fmt.Errorf("%s: missing @route", src)
}
return page, nil
}
-func derivedPageID(source string) string {
- if strings.TrimSpace(source) == "" {
+func derivedPageID(src string) string {
+ if strings.TrimSpace(src) == "" {
return ""
}
- base := filepath.Base(source)
+ base := filepath.Base(src)
if base == "." || base == string(filepath.Separator) {
return ""
}
@@ -145,8 +146,8 @@ func derivedPageID(source string) string {
}
// LowerComponent lowers one component AST into manifest compatibility records.
-func LowerComponent(source string, ast gwdkast.File) (manifest.Component, error) {
- component := manifest.Component{Source: source}
+func LowerComponent(src string, ast gwdkast.File) (manifest.Component, error) {
+ component := manifest.Component{Source: src}
if ast.Package != nil {
component.Package = ast.Package.Name
component.PackageSpan = ast.Package.Span
@@ -155,21 +156,21 @@ func LowerComponent(source string, ast gwdkast.File) (manifest.Component, error)
component.Uses = lowerUses(ast.Uses)
for _, asset := range ast.CSS {
component.CSS = append(component.CSS, asset.Path)
- component.Spans.CSS = append(component.Spans.CSS, manifest.NamedSpan{Name: asset.Path, Span: asset.Span})
+ component.Spans.CSS = append(component.Spans.CSS, source.NamedSpan{Name: asset.Path, Span: asset.Span})
}
for _, asset := range ast.JS {
if strings.TrimSpace(asset.Path) != "" {
component.JS = append(component.JS, asset.Path)
- component.Spans.JS = append(component.Spans.JS, manifest.NamedSpan{Name: asset.Path, Span: asset.Span})
+ component.Spans.JS = append(component.Spans.JS, source.NamedSpan{Name: asset.Path, Span: asset.Span})
continue
}
- name := manifest.InlineScriptName(len(component.InlineJS))
- component.InlineJS = append(component.InlineJS, manifest.InlineScript{Name: name, Body: asset.Inline, Span: asset.Span})
- component.Spans.InlineJS = append(component.Spans.InlineJS, manifest.NamedSpan{Name: name, Span: asset.Span})
+ name := source.InlineScriptName(len(component.InlineJS))
+ component.InlineJS = append(component.InlineJS, source.InlineScript{Name: name, Body: asset.Inline, Span: asset.Span})
+ component.Spans.InlineJS = append(component.Spans.InlineJS, source.NamedSpan{Name: name, Span: asset.Span})
}
for _, asset := range ast.Assets {
component.Assets = append(component.Assets, asset.Path)
- component.Spans.Assets = append(component.Spans.Assets, manifest.NamedSpan{Name: asset.Path, Span: asset.Span})
+ component.Spans.Assets = append(component.Spans.Assets, source.NamedSpan{Name: asset.Path, Span: asset.Span})
}
if ast.PropsType != nil {
component.PropsType = lowerGoTypeRef(*ast.PropsType)
@@ -217,7 +218,7 @@ func LowerComponent(source string, ast gwdkast.File) (manifest.Component, error)
component.Spans.Assets = namedSpans(component.Assets, annotation.Span)
}
default:
- return manifest.Component{}, fmt.Errorf("%s: unsupported component annotation @%s", source, annotation.Name)
+ return manifest.Component{}, fmt.Errorf("%s: unsupported component annotation @%s", src, annotation.Name)
}
}
for _, block := range ast.Blocks {
@@ -240,7 +241,7 @@ func LowerComponent(source string, ast gwdkast.File) (manifest.Component, error)
Body: block.Body,
Span: block.Span,
})
- component.Blocks.Spans.GoBlocks = append(component.Blocks.Spans.GoBlocks, manifest.NamedSpan{Name: block.Name, Span: block.Span})
+ component.Blocks.Spans.GoBlocks = append(component.Blocks.Spans.GoBlocks, source.NamedSpan{Name: block.Name, Span: block.Span})
case "view":
component.Blocks.View = true
component.Blocks.ViewBody = block.Body
@@ -250,18 +251,18 @@ func LowerComponent(source string, ast gwdkast.File) (manifest.Component, error)
component.Blocks.Style = strings.TrimSpace(block.StyleBody) != ""
component.Blocks.StyleBody = block.StyleBody
default:
- return manifest.Component{}, fmt.Errorf("%s: unsupported component block %q", source, block.Kind)
+ return manifest.Component{}, fmt.Errorf("%s: unsupported component block %q", src, block.Kind)
}
}
if component.Name == "" {
- return manifest.Component{}, fmt.Errorf("%s: missing @component", source)
+ return manifest.Component{}, fmt.Errorf("%s: missing @component", src)
}
return component, nil
}
// LowerLayout lowers one layout AST into manifest compatibility records.
-func LowerLayout(source string, ast gwdkast.File) (manifest.Layout, error) {
- layout := manifest.Layout{Source: source}
+func LowerLayout(src string, ast gwdkast.File) (manifest.Layout, error) {
+ layout := manifest.Layout{Source: src}
if ast.Package != nil {
layout.Package = ast.Package.Name
layout.PackageSpan = ast.Package.Span
@@ -280,7 +281,7 @@ func LowerLayout(source string, ast gwdkast.File) (manifest.Layout, error) {
layout.ID = trimQuotes(annotation.Value)
layout.Span = annotation.Span
default:
- return manifest.Layout{}, fmt.Errorf("%s: unsupported layout annotation @%s", source, annotation.Name)
+ return manifest.Layout{}, fmt.Errorf("%s: unsupported layout annotation @%s", src, annotation.Name)
}
}
for _, block := range ast.Blocks {
@@ -290,7 +291,7 @@ func LowerLayout(source string, ast gwdkast.File) (manifest.Layout, error) {
Body: block.Body,
Span: block.Span,
})
- layout.Blocks.Spans.GoBlocks = append(layout.Blocks.Spans.GoBlocks, manifest.NamedSpan{Name: block.Name, Span: block.Span})
+ layout.Blocks.Spans.GoBlocks = append(layout.Blocks.Spans.GoBlocks, source.NamedSpan{Name: block.Name, Span: block.Span})
continue
}
if block.Kind == "style" {
@@ -299,7 +300,7 @@ func LowerLayout(source string, ast gwdkast.File) (manifest.Layout, error) {
continue
}
if block.Kind != "view" {
- return manifest.Layout{}, fmt.Errorf("%s: unsupported layout block %q", source, block.Kind)
+ return manifest.Layout{}, fmt.Errorf("%s: unsupported layout block %q", src, block.Kind)
}
layout.Blocks.View = true
layout.Blocks.ViewBody = block.Body
@@ -307,7 +308,7 @@ func LowerLayout(source string, ast gwdkast.File) (manifest.Layout, error) {
layout.Blocks.Spans.ViewBodyStart = block.BodyStart
}
if layout.ID == "" {
- return manifest.Layout{}, fmt.Errorf("%s: missing @layout", source)
+ return manifest.Layout{}, fmt.Errorf("%s: missing @layout", src)
}
return layout, nil
}
@@ -413,7 +414,7 @@ func applyPageBlock(page *manifest.Page, block gwdkast.Block) {
Body: block.Body,
Span: block.Span,
})
- page.Blocks.Spans.GoBlocks = append(page.Blocks.Spans.GoBlocks, manifest.NamedSpan{Name: block.Name, Span: block.Span})
+ page.Blocks.Spans.GoBlocks = append(page.Blocks.Spans.GoBlocks, source.NamedSpan{Name: block.Name, Span: block.Span})
case "view":
page.Blocks.View = true
page.Blocks.ViewBody = block.Body
@@ -454,22 +455,22 @@ func lowerStores(in []gwdkast.Store) []manifest.Store {
return out
}
-func lowerRouteParamSpans(in []gwdkast.RouteParam) []manifest.NamedSpan {
- out := make([]manifest.NamedSpan, 0, len(in))
+func lowerRouteParamSpans(in []gwdkast.RouteParam) []source.NamedSpan {
+ out := make([]source.NamedSpan, 0, len(in))
for _, param := range in {
- out = append(out, manifest.NamedSpan{Name: param.Name, Span: param.Span})
+ out = append(out, source.NamedSpan{Name: param.Name, Span: param.Span})
}
return out
}
-func lowerRouteParams(in []gwdkast.RouteParam) []manifest.RouteParam {
- out := make([]manifest.RouteParam, 0, len(in))
+func lowerRouteParams(in []gwdkast.RouteParam) []source.RouteParam {
+ out := make([]source.RouteParam, 0, len(in))
for _, param := range in {
paramType := param.Type
if paramType == "" {
paramType = "string"
}
- out = append(out, manifest.RouteParam{Name: param.Name, Type: paramType, Span: param.Span})
+ out = append(out, source.RouteParam{Name: param.Name, Type: paramType, Span: param.Span})
}
return out
}
diff --git a/internal/gwdkanalysis/routes.go b/internal/gwdkanalysis/routes.go
index 1ef09af..68abf3c 100644
--- a/internal/gwdkanalysis/routes.go
+++ b/internal/gwdkanalysis/routes.go
@@ -4,6 +4,7 @@ import (
"sort"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func routeParams(route string) []string {
@@ -20,9 +21,9 @@ func routeParams(route string) []string {
return out
}
-func typedRouteParams(route string) []manifest.RouteParam {
+func typedRouteParams(route string) []source.RouteParam {
params := manifest.RouteParamsFromPath(route)
- out := make([]manifest.RouteParam, 0, len(params))
+ out := make([]source.RouteParam, 0, len(params))
seen := map[string]bool{}
for _, param := range params {
if seen[param.Name] {
@@ -34,19 +35,19 @@ func typedRouteParams(route string) []manifest.RouteParam {
return out
}
-func routeParamSpans(route string, fallback manifest.SourceSpan) []manifest.NamedSpan {
+func routeParamSpans(route string, fallback source.SourceSpan) []source.NamedSpan {
params := routeParams(route)
- out := make([]manifest.NamedSpan, 0, len(params))
+ out := make([]source.NamedSpan, 0, len(params))
for _, param := range params {
- out = append(out, manifest.NamedSpan{Name: param, Span: fallback})
+ out = append(out, source.NamedSpan{Name: param, Span: fallback})
}
return out
}
-func namedSpans(values []string, fallback manifest.SourceSpan) []manifest.NamedSpan {
- out := make([]manifest.NamedSpan, 0, len(values))
+func namedSpans(values []string, fallback source.SourceSpan) []source.NamedSpan {
+ out := make([]source.NamedSpan, 0, len(values))
for _, value := range values {
- out = append(out, manifest.NamedSpan{Name: value, Span: fallback})
+ out = append(out, source.NamedSpan{Name: value, Span: fallback})
}
return out
}
diff --git a/internal/gwdkanalysis/util.go b/internal/gwdkanalysis/util.go
index fd3d434..2d62c1b 100644
--- a/internal/gwdkanalysis/util.go
+++ b/internal/gwdkanalysis/util.go
@@ -8,10 +8,11 @@ import (
"github.com/cssbruno/gowdk/internal/gwdkir"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
-func endpointSource(source manifest.EndpointSource) gwdkir.EndpointSource {
- if source == manifest.EndpointSourceGo {
+func endpointSource(src manifest.EndpointSource) gwdkir.EndpointSource {
+ if src == manifest.EndpointSourceGo {
return gwdkir.EndpointSourceGo
}
return gwdkir.EndpointSourceGOWDK
@@ -97,7 +98,7 @@ func revalidateSecondsValue(value string) (string, error) {
return strconv.FormatInt(int64(duration/time.Second), 10), nil
}
-func spanForName(spans []manifest.NamedSpan, name string, fallback manifest.SourceSpan) manifest.SourceSpan {
+func spanForName(spans []source.NamedSpan, name string, fallback source.SourceSpan) source.SourceSpan {
for _, span := range spans {
if span.Name == name {
return span.Span
diff --git a/internal/gwdkast/ast.go b/internal/gwdkast/ast.go
index 7b78cd3..862a0d5 100644
--- a/internal/gwdkast/ast.go
+++ b/internal/gwdkast/ast.go
@@ -2,7 +2,7 @@
package gwdkast
import (
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/internal/view"
)
@@ -37,76 +37,76 @@ type File struct {
// Package is the top-level Go package declaration.
type Package struct {
Name string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Annotation is one top-level @annotation.
type Annotation struct {
Name string
Value string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// PageDecl is an @page declaration.
type PageDecl struct {
ID string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// ComponentDecl is an @component declaration.
type ComponentDecl struct {
Name string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// LayoutDecl is an @layout declaration in a layout file.
type LayoutDecl struct {
ID string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// RouteDecl is an @route declaration.
type RouteDecl struct {
Path string
Params []RouteParam
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// RouteParam is one dynamic route segment declared by @route.
type RouteParam struct {
Name string
Type string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// CacheDecl is an @cache route response policy declaration.
type CacheDecl struct {
Policy string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// RevalidateDecl is an @revalidate stale-while-revalidate declaration.
type RevalidateDecl struct {
Seconds string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// ErrorPageDecl is a route-local generated error page path.
type ErrorPageDecl struct {
Path string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// LayoutRef is one @layout reference on a page.
type LayoutRef struct {
ID string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// GuardRef is one @guard reference on a page.
type GuardRef struct {
Name string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// AssetRef is one source-selected asset reference.
@@ -115,7 +115,7 @@ type AssetRef struct {
Path string
Inline string
Scope AssetScope
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// AssetScope records deterministic owner metadata for scoped CSS and future
@@ -132,28 +132,28 @@ type AssetScope struct {
type Import struct {
Alias string
Path string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Use is one top-level GOWDK package import declaration.
type Use struct {
Alias string
Package string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// GoTypeRef references a Go type through a .gwdk import alias.
type GoTypeRef struct {
Alias string
Name string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// GoFuncRef references a Go function through a .gwdk import alias.
type GoFuncRef struct {
Alias string
Name string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Store is one top-level page-scoped store declaration.
@@ -161,20 +161,20 @@ type Store struct {
Name string
Type GoTypeRef
Init GoFuncRef
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// StateContract describes a component state type and initializer.
type StateContract struct {
Type GoTypeRef
Init GoFuncRef
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// WASMContract points an explicit browser-side Go package at a component.
type WASMContract struct {
Package string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Block is one parsed top-level block.
@@ -182,8 +182,8 @@ type Block struct {
Kind string
Name string
Body string
- Span manifest.SourceSpan
- BodyStart manifest.SourcePosition
+ Span source.SourceSpan
+ BodyStart source.SourcePosition
View []view.Node
StyleBody string
Records []LiteralRecord
@@ -202,8 +202,8 @@ type Endpoint struct {
Method string
Route string
ErrorPage string
- Span manifest.SourceSpan
- ErrorPageSpan manifest.SourceSpan
+ Span source.SourceSpan
+ ErrorPageSpan source.SourceSpan
}
// FragmentEndpoint is one generated server fragment route declaration.
@@ -213,50 +213,50 @@ type FragmentEndpoint struct {
Route string
Target string
Body string
- Span manifest.SourceSpan
- RouteSpan manifest.SourceSpan
- TargetSpan manifest.SourceSpan
+ Span source.SourceSpan
+ RouteSpan source.SourceSpan
+ TargetSpan source.SourceSpan
}
// LiteralRecord is a first-slice paths/build return record.
type LiteralRecord struct {
Fields map[string]string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// BuildCall is a first-slice imported build data function call.
type BuildCall struct {
Alias string
Function string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Prop is one scalar prop declaration inside props {}.
type Prop struct {
Name string
Type string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Export is one typed public component export inside exports {}.
type Export struct {
Name string
Type string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Emit is one component event declaration inside emits {}.
type Emit struct {
Name string
Params []EmitParam
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// EmitParam is one typed event payload field.
type EmitParam struct {
Name string
Type string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// ActionStatement is one supported statement inside legacy act {} parsing.
@@ -268,12 +268,12 @@ type ActionStatement struct {
Target string
Redirect string
Body string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// APIStatement is one supported statement inside legacy api {} parsing.
type APIStatement struct {
Method string
Route string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
diff --git a/internal/gwdkir/ir.go b/internal/gwdkir/ir.go
index db87708..5de2219 100644
--- a/internal/gwdkir/ir.go
+++ b/internal/gwdkir/ir.go
@@ -4,7 +4,7 @@ package gwdkir
import (
"github.com/cssbruno/gowdk"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
const Version = 1
@@ -41,7 +41,7 @@ type SourceFile struct {
Kind SourceKind
Package string
Name string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type SourceKind string
@@ -56,14 +56,14 @@ const (
type Import struct {
Alias string
Path string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Use records an explicit GOWDK package import.
type Use struct {
Alias string
Package string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Store records one shared state declaration.
@@ -71,7 +71,7 @@ type Store struct {
Name string
Type GoRef
Init GoRef
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Page is the normalized IR for one page source.
@@ -80,7 +80,7 @@ type Page struct {
Package string
ID string
Route string
- RouteParams []manifest.RouteParam
+ RouteParams []source.RouteParam
Render gowdk.RenderMode
Cache string
Revalidate string
@@ -90,7 +90,7 @@ type Page struct {
Guards []string
CSS []string
JS []string
- InlineJS []manifest.InlineScript
+ InlineJS []source.InlineScript
Imports []Import
Uses []Use
Stores []Store
@@ -130,42 +130,42 @@ type Blocks struct {
type GoBlock struct {
Target string
Body string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type PageSpans struct {
- Package manifest.SourceSpan
- Page manifest.SourceSpan
- Route manifest.SourceSpan
- Render manifest.SourceSpan
- Cache manifest.SourceSpan
- Revalidate manifest.SourceSpan
- ErrorPage manifest.SourceSpan
- Title manifest.SourceSpan
- Description manifest.SourceSpan
- Canonical manifest.SourceSpan
- Image manifest.SourceSpan
- Layouts []manifest.NamedSpan
- Guard []manifest.NamedSpan
- CSS []manifest.NamedSpan
- JS []manifest.NamedSpan
- InlineJS []manifest.NamedSpan
- RouteParams []manifest.NamedSpan
+ Package source.SourceSpan
+ Page source.SourceSpan
+ Route source.SourceSpan
+ Render source.SourceSpan
+ Cache source.SourceSpan
+ Revalidate source.SourceSpan
+ ErrorPage source.SourceSpan
+ Title source.SourceSpan
+ Description source.SourceSpan
+ Canonical source.SourceSpan
+ Image source.SourceSpan
+ Layouts []source.NamedSpan
+ Guard []source.NamedSpan
+ CSS []source.NamedSpan
+ JS []source.NamedSpan
+ InlineJS []source.NamedSpan
+ RouteParams []source.NamedSpan
}
type BlockSpans struct {
- Paths manifest.SourceSpan
- Build manifest.SourceSpan
- Load manifest.SourceSpan
- Client manifest.SourceSpan
- GoBlocks []manifest.NamedSpan
- View manifest.SourceSpan
- ViewBodyStart manifest.SourcePosition
- Actions []manifest.NamedSpan
- APIs []manifest.NamedSpan
- Fragments []manifest.NamedSpan
- Exports manifest.SourceSpan
- Emits manifest.SourceSpan
+ Paths source.SourceSpan
+ Build source.SourceSpan
+ Load source.SourceSpan
+ Client source.SourceSpan
+ GoBlocks []source.NamedSpan
+ View source.SourceSpan
+ ViewBodyStart source.SourcePosition
+ Actions []source.NamedSpan
+ APIs []source.NamedSpan
+ Fragments []source.NamedSpan
+ Exports source.SourceSpan
+ Emits source.SourceSpan
}
type Action struct {
@@ -179,19 +179,19 @@ type Action struct {
Redirect string
Fragments []Fragment
ErrorPage string
- Span manifest.SourceSpan
- RouteSpan manifest.SourceSpan
- RouteParams []manifest.NamedSpan
- InputSpan manifest.SourceSpan
- ValidationSpan manifest.SourceSpan
- RedirectSpan manifest.SourceSpan
- ErrorPageSpan manifest.SourceSpan
+ Span source.SourceSpan
+ RouteSpan source.SourceSpan
+ RouteParams []source.NamedSpan
+ InputSpan source.SourceSpan
+ ValidationSpan source.SourceSpan
+ RedirectSpan source.SourceSpan
+ ErrorPageSpan source.SourceSpan
}
type Fragment struct {
Target string
Body string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type FragmentEndpoint struct {
@@ -200,10 +200,10 @@ type FragmentEndpoint struct {
Route string
Target string
Body string
- Span manifest.SourceSpan
- RouteSpan manifest.SourceSpan
- TargetSpan manifest.SourceSpan
- RouteParams []manifest.NamedSpan
+ Span source.SourceSpan
+ RouteSpan source.SourceSpan
+ TargetSpan source.SourceSpan
+ RouteParams []source.NamedSpan
}
type API struct {
@@ -211,10 +211,10 @@ type API struct {
Method string
Route string
ErrorPage string
- Span manifest.SourceSpan
- RouteSpan manifest.SourceSpan
- RouteParams []manifest.NamedSpan
- ErrorPageSpan manifest.SourceSpan
+ Span source.SourceSpan
+ RouteSpan source.SourceSpan
+ RouteParams []source.NamedSpan
+ ErrorPageSpan source.SourceSpan
}
// Component is the normalized IR for one component source.
@@ -226,7 +226,7 @@ type Component struct {
Uses []Use
CSS []string
JS []string
- InlineJS []manifest.InlineScript
+ InlineJS []source.InlineScript
Assets []string
Props []Prop
PropsType GoRef
@@ -235,51 +235,51 @@ type Component struct {
Exports []Export
Emits []Emit
Blocks Blocks
- Span manifest.SourceSpan
- PackageSpan manifest.SourceSpan
+ Span source.SourceSpan
+ PackageSpan source.SourceSpan
Spans ComponentSpans
}
type ComponentSpans struct {
- CSS []manifest.NamedSpan
- JS []manifest.NamedSpan
- InlineJS []manifest.NamedSpan
- Assets []manifest.NamedSpan
+ CSS []source.NamedSpan
+ JS []source.NamedSpan
+ InlineJS []source.NamedSpan
+ Assets []source.NamedSpan
}
type StateContract struct {
Type GoRef
Init GoRef
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type WASMContract struct {
Package string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type Prop struct {
Name string
Type string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type Export struct {
Name string
Type string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type Emit struct {
Name string
Params []EmitParam
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type EmitParam struct {
Name string
Type string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Layout is the normalized IR for one layout source.
@@ -289,15 +289,15 @@ type Layout struct {
ID string
Uses []Use
Blocks Blocks
- Span manifest.SourceSpan
- PackageSpan manifest.SourceSpan
+ Span source.SourceSpan
+ PackageSpan source.SourceSpan
}
// GoRef points at an imported Go package symbol.
type GoRef struct {
Alias string
Name string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Route is page/file route metadata. Endpoint behavior is represented by
@@ -311,11 +311,11 @@ type Route struct {
Render gowdk.RenderMode
Cache string
DynamicParams []string
- RouteParams []manifest.RouteParam
+ RouteParams []source.RouteParam
Layouts []string
Guards []string
Source string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type RouteKind string
@@ -339,7 +339,7 @@ type Endpoint struct {
ErrorPage string
DynamicParams []string
SourceFile string
- Span manifest.SourceSpan
+ Span source.SourceSpan
Binding Binding
}
@@ -360,15 +360,15 @@ const (
// Binding describes the selected Go backend handler when one is known.
type Binding struct {
- Status manifest.BackendBindingStatus
+ Status source.BackendBindingStatus
Message string
ImportPath string
PackageName string
FunctionName string
- Signature manifest.BackendSignatureKind
+ Signature source.BackendSignatureKind
InputType string
InputPointer bool
- InputFields []manifest.BackendInputField
+ InputFields []source.BackendInputField
}
// Template records a renderable view block.
@@ -381,8 +381,8 @@ type Template struct {
Guards []string
Imports []Import
Body string
- Span manifest.SourceSpan
- BodyStart manifest.SourcePosition
+ Span source.SourceSpan
+ BodyStart source.SourcePosition
}
// ContractReference records a source-level reference to a backend contract.
@@ -396,7 +396,7 @@ type ContractReference struct {
Result string
Roles []string
Guards []string
- InputFields []manifest.BackendInputField
+ InputFields []source.BackendInputField
Method string
Path string
Status ContractBindingStatus
@@ -407,7 +407,7 @@ type ContractReference struct {
OwnerID string
Package string
Source string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type ContractKind string
@@ -433,7 +433,7 @@ type ClientBehavior struct {
Package string
Source string
Body string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
// Asset records source-selected assets and future generated assets.
@@ -449,7 +449,7 @@ type Asset struct {
UsePackage string
ScopeID string
HashKey string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
type AssetKind string
diff --git a/internal/gwdkir/page_methods.go b/internal/gwdkir/page_methods.go
new file mode 100644
index 0000000..ca755d2
--- /dev/null
+++ b/internal/gwdkir/page_methods.go
@@ -0,0 +1,173 @@
+package gwdkir
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/cssbruno/gowdk"
+ "github.com/cssbruno/gowdk/internal/source"
+)
+
+// These methods mirror the behavior previously provided by manifest.Page so
+// generated-output packages can consume the IR page model directly instead of a
+// reconstructed manifest. They read IR fields only and depend on no other model.
+
+// CachePolicy returns the concrete Cache-Control policy generated for the page.
+func (page Page) CachePolicy() string {
+ return CachePolicyWithRevalidate(page.Cache, page.Revalidate)
+}
+
+// CachePolicyWithRevalidate appends the page revalidation directive to an
+// explicit Cache-Control policy.
+func CachePolicyWithRevalidate(cache string, revalidate string) string {
+ if cache == "" || revalidate == "" {
+ return cache
+ }
+ return cache + ", stale-while-revalidate=" + revalidate
+}
+
+// RenderMode resolves the effective render mode for the page, defaulting to SSR
+// when the page declares request-time behavior and otherwise to defaultMode
+// (SPA when unset).
+func (page Page) RenderMode(defaultMode gowdk.RenderMode) gowdk.RenderMode {
+ if page.Render != "" {
+ return page.Render
+ }
+ if page.Blocks.Load || page.HasGoBlock("ssr") {
+ return gowdk.SSR
+ }
+ if defaultMode == "" {
+ return gowdk.SPA
+ }
+ return defaultMode
+}
+
+// HasGoBlock reports whether the page declares a go block for target.
+func (page Page) HasGoBlock(target string) bool {
+ for _, block := range page.Blocks.GoBlocks {
+ if block.Target == target {
+ return true
+ }
+ }
+ return false
+}
+
+// DynamicParams returns route parameters declared with /path/{param} syntax.
+func (page Page) DynamicParams() []string {
+ if len(page.RouteParams) > 0 {
+ params := make([]string, 0, len(page.RouteParams))
+ seen := map[string]bool{}
+ for _, param := range page.RouteParams {
+ if param.Name == "" || seen[param.Name] {
+ continue
+ }
+ seen[param.Name] = true
+ params = append(params, param.Name)
+ }
+ sort.Strings(params)
+ return params
+ }
+ params := RouteParamsFromPath(page.Route)
+ if len(params) == 0 {
+ return nil
+ }
+ names := make([]string, 0, len(params))
+ seen := map[string]bool{}
+ for _, param := range params {
+ if !seen[param.Name] {
+ seen[param.Name] = true
+ names = append(names, param.Name)
+ }
+ }
+ sort.Strings(names)
+ return names
+}
+
+// TypedRouteParams returns route params with explicit type metadata. Untyped
+// params are reported as string.
+func (page Page) TypedRouteParams() []source.RouteParam {
+ if len(page.RouteParams) > 0 {
+ out := make([]source.RouteParam, 0, len(page.RouteParams))
+ for _, param := range page.RouteParams {
+ if param.Name == "" {
+ continue
+ }
+ if param.Type == "" {
+ param.Type = "string"
+ }
+ out = append(out, param)
+ }
+ return out
+ }
+ params := RouteParamsFromPath(page.Route)
+ if len(params) == 0 {
+ return nil
+ }
+ out := make([]source.RouteParam, 0, len(params))
+ seen := map[string]bool{}
+ for _, param := range params {
+ if seen[param.Name] {
+ continue
+ }
+ seen[param.Name] = true
+ if param.Type == "" {
+ param.Type = "string"
+ }
+ out = append(out, param)
+ }
+ return out
+}
+
+// RouteParamsFromPath parses dynamic route parameters from a route pattern of
+// the form /path/{name} or /path/{name:type}.
+func RouteParamsFromPath(route string) []source.RouteParam {
+ var params []source.RouteParam
+ for index := 0; index < len(route); index++ {
+ if route[index] != '{' {
+ continue
+ }
+ end := strings.IndexByte(route[index:], '}')
+ if end < 0 {
+ continue
+ }
+ end += index
+ body := route[index+1 : end]
+ name, paramType, ok := splitRouteParamBody(body)
+ if ok {
+ params = append(params, source.RouteParam{Name: name, Type: paramType})
+ }
+ index = end
+ }
+ return params
+}
+
+func splitRouteParamBody(body string) (string, string, bool) {
+ name := body
+ paramType := "string"
+ if before, after, ok := strings.Cut(body, ":"); ok {
+ name = before
+ paramType = after
+ }
+ if !isRouteIdent(name) || !isRouteIdent(paramType) {
+ return "", "", false
+ }
+ return name, paramType, true
+}
+
+func isRouteIdent(value string) bool {
+ if value == "" {
+ return false
+ }
+ for index, r := range value {
+ if index == 0 {
+ if r != '_' && (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') {
+ return false
+ }
+ continue
+ }
+ if r != '_' && (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') && (r < '0' || r > '9') {
+ return false
+ }
+ }
+ return true
+}
diff --git a/internal/gwdkir/page_methods_test.go b/internal/gwdkir/page_methods_test.go
new file mode 100644
index 0000000..c234a24
--- /dev/null
+++ b/internal/gwdkir/page_methods_test.go
@@ -0,0 +1,63 @@
+package gwdkir
+
+import (
+ "reflect"
+ "testing"
+
+ "github.com/cssbruno/gowdk"
+ "github.com/cssbruno/gowdk/internal/source"
+)
+
+func TestPageRenderModeResolvesRequestTime(t *testing.T) {
+ cases := []struct {
+ name string
+ page Page
+ def gowdk.RenderMode
+ want gowdk.RenderMode
+ }{
+ {"explicit", Page{Render: gowdk.SSR}, gowdk.SPA, gowdk.SSR},
+ {"load_block", Page{Blocks: Blocks{Load: true}}, gowdk.SPA, gowdk.SSR},
+ {"go_ssr_block", Page{Blocks: Blocks{GoBlocks: []GoBlock{{Target: "ssr"}}}}, gowdk.SPA, gowdk.SSR},
+ {"default_spa", Page{}, "", gowdk.SPA},
+ {"default_passthrough", Page{}, gowdk.Action, gowdk.Action},
+ }
+ for _, tc := range cases {
+ if got := tc.page.RenderMode(tc.def); got != tc.want {
+ t.Fatalf("%s: RenderMode = %q, want %q", tc.name, got, tc.want)
+ }
+ }
+}
+
+func TestPageDynamicParamsFromExplicitAndPath(t *testing.T) {
+ explicit := Page{RouteParams: []source.RouteParam{{Name: "slug"}, {Name: "id"}, {Name: "slug"}}}
+ if got := explicit.DynamicParams(); !reflect.DeepEqual(got, []string{"id", "slug"}) {
+ t.Fatalf("explicit DynamicParams = %v, want [id slug]", got)
+ }
+
+ fromPath := Page{Route: "/blog/{slug}/{id:int}"}
+ if got := fromPath.DynamicParams(); !reflect.DeepEqual(got, []string{"id", "slug"}) {
+ t.Fatalf("path DynamicParams = %v, want [id slug]", got)
+ }
+
+ if got := (Page{Route: "/"}).DynamicParams(); got != nil {
+ t.Fatalf("static route DynamicParams = %v, want nil", got)
+ }
+}
+
+func TestPageTypedRouteParamsDefaultsToString(t *testing.T) {
+ page := Page{Route: "/blog/{slug}/{id:int}"}
+ got := page.TypedRouteParams()
+ want := []source.RouteParam{{Name: "slug", Type: "string"}, {Name: "id", Type: "int"}}
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("TypedRouteParams = %#v, want %#v", got, want)
+ }
+}
+
+func TestPageCachePolicy(t *testing.T) {
+ if got := (Page{Cache: "public"}).CachePolicy(); got != "public" {
+ t.Fatalf("CachePolicy = %q", got)
+ }
+ if got := (Page{Cache: "public", Revalidate: "60"}).CachePolicy(); got != "public, stale-while-revalidate=60" {
+ t.Fatalf("CachePolicy with revalidate = %q", got)
+ }
+}
diff --git a/internal/lsp/components.go b/internal/lsp/components.go
index 4bb5d39..9b1cd03 100644
--- a/internal/lsp/components.go
+++ b/internal/lsp/components.go
@@ -9,6 +9,7 @@ import (
"github.com/cssbruno/gowdk/internal/gwdkanalysis"
"github.com/cssbruno/gowdk/internal/lang"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
type componentDefinition struct {
@@ -16,7 +17,7 @@ type componentDefinition struct {
Text string
Package string
Name string
- Span manifest.SourceSpan
+ Span source.SourceSpan
}
func (server *Server) resolveComponentDefinition(doc document, name string) (componentDefinition, bool) {
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
index 1caaf3f..e38c5e2 100644
--- a/internal/lsp/diagnostics.go
+++ b/internal/lsp/diagnostics.go
@@ -5,16 +5,16 @@ import (
"unicode/utf16"
"github.com/cssbruno/gowdk/internal/lang"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
-func diagnosticFromLang(item lang.Diagnostic, source string) diagnostic {
+func diagnosticFromLang(item lang.Diagnostic, body string) diagnostic {
severity := diagnosticSeverityError
if item.Severity == "warning" {
severity = diagnosticSeverityWarning
}
return diagnostic{
- Range: rangeFromLangDiagnostic(item, source),
+ Range: rangeFromLangDiagnostic(item, body),
Severity: severity,
Code: item.Code,
Source: "gowdk",
@@ -22,30 +22,30 @@ func diagnosticFromLang(item lang.Diagnostic, source string) diagnostic {
}
}
-func rangeFromLangDiagnostic(item lang.Diagnostic, source string) lspRange {
+func rangeFromLangDiagnostic(item lang.Diagnostic, body string) lspRange {
if item.Range != nil {
- return rangeFromLangRange(*item.Range, source)
+ return rangeFromLangRange(*item.Range, body)
}
- return rangeFromPosition(item.Pos, source)
+ return rangeFromPosition(item.Pos, body)
}
-func rangeFromLangRange(item lang.Range, source string) lspRange {
- start := positionFromLangPosition(item.Start, source)
- end := positionFromLangPosition(item.End, source)
+func rangeFromLangRange(item lang.Range, body string) lspRange {
+ start := positionFromLangPosition(item.Start, body)
+ end := positionFromLangPosition(item.End, body)
if end.Line < start.Line || (end.Line == start.Line && end.Character <= start.Character) {
end = position{Line: start.Line, Character: start.Character + 1}
}
return lspRange{Start: start, End: end}
}
-func lspRangeFromSourceSpan(span manifest.SourceSpan, source string) lspRange {
+func lspRangeFromSourceSpan(span source.SourceSpan, body string) lspRange {
return rangeFromLangRange(lang.Range{
Start: lang.Position{Line: span.Start.Line, Column: span.Start.Column},
End: lang.Position{Line: span.End.Line, Column: span.End.Column},
- }, source)
+ }, body)
}
-func rangeFromPosition(pos lang.Position, source string) lspRange {
+func rangeFromPosition(pos lang.Position, body string) lspRange {
if pos.Line <= 0 {
return lspRange{
Start: position{Line: 0, Character: 0},
@@ -53,7 +53,7 @@ func rangeFromPosition(pos lang.Position, source string) lspRange {
}
}
- lines := strings.Split(source, "\n")
+ lines := strings.Split(body, "\n")
lineIndex := clamp(pos.Line-1, 0, len(lines)-1)
character := 0
if pos.Column > 1 && len(lines) > 0 {
@@ -73,11 +73,11 @@ func rangeFromPosition(pos lang.Position, source string) lspRange {
}
}
-func positionFromLangPosition(pos lang.Position, source string) position {
+func positionFromLangPosition(pos lang.Position, body string) position {
if pos.Line <= 0 {
return position{Line: 0, Character: 0}
}
- lines := strings.Split(source, "\n")
+ lines := strings.Split(body, "\n")
lineIndex := clamp(pos.Line-1, 0, len(lines)-1)
character := 0
if pos.Column > 1 && len(lines) > 0 {
diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go
index 18039ef..defe926 100644
--- a/internal/manifest/manifest.go
+++ b/internal/manifest/manifest.go
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/cssbruno/gowdk"
+ "github.com/cssbruno/gowdk/internal/source"
)
// Manifest is the compiler's normalized view of discovered .gwdk files.
@@ -18,23 +19,17 @@ type Manifest struct {
BackendBindings []BackendBinding
}
+// The shared leaf value types now live in internal/source. These aliases keep
+// existing manifest.* references compiling while packages migrate to source.*.
+
// SourcePosition is a 1-based source location in a parsed .gwdk file.
-type SourcePosition struct {
- Line int
- Column int
-}
+type SourcePosition = source.SourcePosition
// SourceSpan is a 1-based source range. End is exclusive.
-type SourceSpan struct {
- Start SourcePosition
- End SourcePosition
-}
+type SourceSpan = source.SourceSpan
// NamedSpan records the source range for a named declaration or reference.
-type NamedSpan struct {
- Name string
- Span SourceSpan
-}
+type NamedSpan = source.NamedSpan
// Import records a Go import declared by a .gwdk page.
type Import struct {
@@ -163,19 +158,12 @@ type Page struct {
// InlineScript records browser module code declared directly inside a .gwdk
// source file. Path-based script declarations should remain preferred.
-type InlineScript struct {
- Name string
- Body string
- Span SourceSpan
-}
+type InlineScript = source.InlineScript
// InlineScriptName returns the deterministic generated filename for the
// zero-based inline browser script declaration index in one source owner.
func InlineScriptName(index int) string {
- if index <= 0 {
- return "inline-gowdk.js"
- }
- return fmt.Sprintf("inline-%d-gowdk.js", index+1)
+ return source.InlineScriptName(index)
}
// CachePolicy returns the concrete Cache-Control policy generated for the page.
@@ -219,11 +207,7 @@ func ErrorPagePath(value string) (string, error) {
// RouteParam describes one dynamic route parameter and its declared scalar
// type. Empty Type means string for compatibility with legacy {name} syntax.
-type RouteParam struct {
- Name string
- Type string
- Span SourceSpan
-}
+type RouteParam = source.RouteParam
// PageMetadata describes HTML document metadata declared by a page.
type PageMetadata struct {
@@ -405,35 +389,31 @@ type EndpointDeclaration struct {
// BackendBindingStatus describes whether a .gwdk backend block has a matching
// same-package Go handler.
-type BackendBindingStatus string
+type BackendBindingStatus = source.BackendBindingStatus
const (
- BackendBindingBound BackendBindingStatus = "bound"
- BackendBindingMissing BackendBindingStatus = "missing"
- BackendBindingUnsupportedSignature BackendBindingStatus = "unsupported_signature"
+ BackendBindingBound = source.BackendBindingBound
+ BackendBindingMissing = source.BackendBindingMissing
+ BackendBindingUnsupportedSignature = source.BackendBindingUnsupportedSignature
)
// BackendSignatureKind describes the supported Go handler shape.
-type BackendSignatureKind string
+type BackendSignatureKind = source.BackendSignatureKind
const (
- BackendSignatureAction0 BackendSignatureKind = "action0"
- BackendSignatureActionValues BackendSignatureKind = "action_values"
- BackendSignatureActionForm BackendSignatureKind = "action_form"
- BackendSignatureActionFormPtr BackendSignatureKind = "action_form_ptr"
- BackendSignatureAPI BackendSignatureKind = "api"
- BackendSignatureFragment BackendSignatureKind = "fragment"
- BackendSignatureLoad BackendSignatureKind = "load"
- BackendSignatureLoadError BackendSignatureKind = "load_error"
+ BackendSignatureAction0 = source.BackendSignatureAction0
+ BackendSignatureActionValues = source.BackendSignatureActionValues
+ BackendSignatureActionForm = source.BackendSignatureActionForm
+ BackendSignatureActionFormPtr = source.BackendSignatureActionFormPtr
+ BackendSignatureAPI = source.BackendSignatureAPI
+ BackendSignatureFragment = source.BackendSignatureFragment
+ BackendSignatureLoad = source.BackendSignatureLoad
+ BackendSignatureLoadError = source.BackendSignatureLoadError
)
// BackendInputField describes one form field decoded into a Go action input
// struct from compile-time Go AST metadata.
-type BackendInputField struct {
- FieldName string
- FormName string
- Type string
-}
+type BackendInputField = source.BackendInputField
// BackendBinding describes the Go handler selected for an act or api block.
type BackendBinding struct {
diff --git a/internal/parser/annotations.go b/internal/parser/annotations.go
index 2cf15d9..a44d0da 100644
--- a/internal/parser/annotations.go
+++ b/internal/parser/annotations.go
@@ -7,6 +7,7 @@ import (
"time"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func applyAnnotation(page *manifest.Page, name, rawValue string, lineNumber int, rawLine string) error {
@@ -126,9 +127,9 @@ func endpointErrorPage(match []string, lineNumber int) (string, error) {
return errorPage, nil
}
-func endpointErrorPageSpan(match []string, fallback manifest.SourceSpan) manifest.SourceSpan {
+func endpointErrorPageSpan(match []string, fallback source.SourceSpan) source.SourceSpan {
if len(match) < 5 || strings.TrimSpace(match[4]) == "" {
- return manifest.SourceSpan{}
+ return source.SourceSpan{}
}
return fallback
}
diff --git a/internal/parser/component.go b/internal/parser/component.go
index 26b9931..562c9eb 100644
--- a/internal/parser/component.go
+++ b/internal/parser/component.go
@@ -7,10 +7,11 @@ import (
"strings"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
// ParseComponent extracts component metadata and top-level block declarations.
-func ParseComponent(source []byte) (manifest.Component, error) {
+func ParseComponent(src []byte) (manifest.Component, error) {
var component manifest.Component
var viewBody []string
inView := false
@@ -30,10 +31,10 @@ func ParseComponent(source []byte) (manifest.Component, error) {
inGoBlock := false
goBlockDepth := 0
goBlockTarget := ""
- seenGoBlocks := map[string]manifest.SourceSpan{}
+ seenGoBlocks := map[string]source.SourceSpan{}
seenDeclaration := false
- scanner := bufio.NewScanner(bytes.NewReader(source))
+ scanner := bufio.NewScanner(bytes.NewReader(src))
for lineNumber := 1; scanner.Scan(); lineNumber++ {
rawLine := scanner.Text()
line := strings.TrimSpace(rawLine)
@@ -46,7 +47,7 @@ func ParseComponent(source []byte) (manifest.Component, error) {
Body: strings.TrimSpace(strings.Join(goBlockBody, "\n")),
Span: seenGoBlocks[goBlockTarget],
})
- component.Blocks.Spans.GoBlocks = append(component.Blocks.Spans.GoBlocks, manifest.NamedSpan{Name: goBlockTarget, Span: seenGoBlocks[goBlockTarget]})
+ component.Blocks.Spans.GoBlocks = append(component.Blocks.Spans.GoBlocks, source.NamedSpan{Name: goBlockTarget, Span: seenGoBlocks[goBlockTarget]})
inGoBlock = false
goBlockBody = nil
goBlockDepth = 0
@@ -98,8 +99,8 @@ func ParseComponent(source []byte) (manifest.Component, error) {
if line == "}" {
jsDepth--
if jsDepth == 0 {
- name := manifest.InlineScriptName(len(component.InlineJS))
- component.InlineJS = append(component.InlineJS, manifest.InlineScript{
+ name := source.InlineScriptName(len(component.InlineJS))
+ component.InlineJS = append(component.InlineJS, source.InlineScript{
Name: name,
Body: strings.TrimSpace(strings.Join(jsBody, "\n")),
Span: component.Spans.InlineJS[len(component.Spans.InlineJS)-1].Span,
@@ -227,13 +228,13 @@ func ParseComponent(source []byte) (manifest.Component, error) {
}
if match := jsPattern.FindStringSubmatch(line); match != nil {
component.JS = append(component.JS, match[1])
- component.Spans.JS = append(component.Spans.JS, manifest.NamedSpan{Name: match[1], Span: sourceLineSpan(lineNumber, rawLine)})
+ component.Spans.JS = append(component.Spans.JS, source.NamedSpan{Name: match[1], Span: sourceLineSpan(lineNumber, rawLine)})
continue
}
if jsBlockPattern.MatchString(line) {
span := sourceLineSpan(lineNumber, rawLine)
- name := manifest.InlineScriptName(len(component.InlineJS))
- component.Spans.InlineJS = append(component.Spans.InlineJS, manifest.NamedSpan{Name: name, Span: span})
+ name := source.InlineScriptName(len(component.InlineJS))
+ component.Spans.InlineJS = append(component.Spans.InlineJS, source.NamedSpan{Name: name, Span: span})
inJS = true
jsDepth = 1
continue
@@ -403,12 +404,12 @@ func parseEmitDeclaration(line string, lineNumber int, rawLine string) (manifest
return manifest.Emit{Name: match[1], Params: params, Span: sourceLineSpan(lineNumber, rawLine)}, nil
}
-func parseEmitParams(source string, lineNumber int, rawLine string) ([]manifest.EmitParam, error) {
- source = strings.TrimSpace(source)
- if source == "" {
+func parseEmitParams(src string, lineNumber int, rawLine string) ([]manifest.EmitParam, error) {
+ src = strings.TrimSpace(src)
+ if src == "" {
return nil, nil
}
- parts := strings.Split(source, ",")
+ parts := strings.Split(src, ",")
params := make([]manifest.EmitParam, 0, len(parts))
seen := map[string]bool{}
for _, part := range parts {
diff --git a/internal/parser/layout.go b/internal/parser/layout.go
index 152ab02..f4c337d 100644
--- a/internal/parser/layout.go
+++ b/internal/parser/layout.go
@@ -7,10 +7,11 @@ import (
"strings"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
// ParseLayout extracts layout metadata and top-level block declarations.
-func ParseLayout(source []byte) (manifest.Layout, error) {
+func ParseLayout(src []byte) (manifest.Layout, error) {
var layout manifest.Layout
var viewBody []string
inView := false
@@ -21,10 +22,10 @@ func ParseLayout(source []byte) (manifest.Layout, error) {
inGoBlock := false
goBlockDepth := 0
goBlockTarget := ""
- seenGoBlocks := map[string]manifest.SourceSpan{}
+ seenGoBlocks := map[string]source.SourceSpan{}
seenDeclaration := false
- scanner := bufio.NewScanner(bytes.NewReader(source))
+ scanner := bufio.NewScanner(bytes.NewReader(src))
for lineNumber := 1; scanner.Scan(); lineNumber++ {
rawLine := scanner.Text()
line := strings.TrimSpace(rawLine)
@@ -37,7 +38,7 @@ func ParseLayout(source []byte) (manifest.Layout, error) {
Body: strings.TrimSpace(strings.Join(goBlockBody, "\n")),
Span: seenGoBlocks[goBlockTarget],
})
- layout.Blocks.Spans.GoBlocks = append(layout.Blocks.Spans.GoBlocks, manifest.NamedSpan{Name: goBlockTarget, Span: seenGoBlocks[goBlockTarget]})
+ layout.Blocks.Spans.GoBlocks = append(layout.Blocks.Spans.GoBlocks, source.NamedSpan{Name: goBlockTarget, Span: seenGoBlocks[goBlockTarget]})
inGoBlock = false
goBlockBody = nil
goBlockDepth = 0
diff --git a/internal/parser/page_lower.go b/internal/parser/page_lower.go
index 55cc1a9..8cf4f39 100644
--- a/internal/parser/page_lower.go
+++ b/internal/parser/page_lower.go
@@ -6,9 +6,10 @@ import (
"github.com/cssbruno/gowdk/internal/gwdkast"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
-func lowerPageSyntax(source []byte, ast gwdkast.File, defaultID string) (manifest.Page, error) {
+func lowerPageSyntax(src []byte, ast gwdkast.File, defaultID string) (manifest.Page, error) {
var page manifest.Page
if ast.Package != nil {
page.Package = ast.Package.Name
@@ -17,14 +18,14 @@ func lowerPageSyntax(source []byte, ast gwdkast.File, defaultID string) (manifes
page.Imports = lowerSyntaxImports(ast.Imports)
page.Uses = lowerSyntaxUses(ast.Uses)
page.Stores = lowerSyntaxStores(ast.Stores)
- if err := lowerPageSyntaxAnnotations(source, ast, &page); err != nil {
+ if err := lowerPageSyntaxAnnotations(src, ast, &page); err != nil {
return manifest.Page{}, err
}
for _, block := range ast.Blocks {
applyPageSyntaxBlock(&page, block)
}
for _, endpoint := range ast.Actions {
- rawLine := sourceLineText(source, endpoint.Span.Start.Line)
+ rawLine := sourceLineText(src, endpoint.Span.Start.Line)
page.Blocks.Actions = append(page.Blocks.Actions, manifest.Action{
Name: endpoint.Name,
Method: endpoint.Method,
@@ -35,10 +36,10 @@ func lowerPageSyntax(source []byte, ast gwdkast.File, defaultID string) (manifes
RouteParams: routeParamSpans(endpoint.Route, endpoint.Span.Start.Line, rawLine),
ErrorPageSpan: endpoint.ErrorPageSpan,
})
- page.Blocks.Spans.Actions = append(page.Blocks.Spans.Actions, manifest.NamedSpan{Name: endpoint.Name, Span: endpoint.Span})
+ page.Blocks.Spans.Actions = append(page.Blocks.Spans.Actions, source.NamedSpan{Name: endpoint.Name, Span: endpoint.Span})
}
for _, endpoint := range ast.APIs {
- rawLine := sourceLineText(source, endpoint.Span.Start.Line)
+ rawLine := sourceLineText(src, endpoint.Span.Start.Line)
page.Blocks.APIs = append(page.Blocks.APIs, manifest.API{
Name: endpoint.Name,
Method: endpoint.Method,
@@ -49,10 +50,10 @@ func lowerPageSyntax(source []byte, ast gwdkast.File, defaultID string) (manifes
RouteParams: routeParamSpans(endpoint.Route, endpoint.Span.Start.Line, rawLine),
ErrorPageSpan: endpoint.ErrorPageSpan,
})
- page.Blocks.Spans.APIs = append(page.Blocks.Spans.APIs, manifest.NamedSpan{Name: endpoint.Name, Span: endpoint.Span})
+ page.Blocks.Spans.APIs = append(page.Blocks.Spans.APIs, source.NamedSpan{Name: endpoint.Name, Span: endpoint.Span})
}
for _, fragment := range ast.Fragments {
- rawLine := sourceLineText(source, fragment.RouteSpan.Start.Line)
+ rawLine := sourceLineText(src, fragment.RouteSpan.Start.Line)
page.Blocks.Fragments = append(page.Blocks.Fragments, manifest.FragmentEndpoint{
Name: fragment.Name,
Method: fragment.Method,
@@ -64,7 +65,7 @@ func lowerPageSyntax(source []byte, ast gwdkast.File, defaultID string) (manifes
TargetSpan: fragment.TargetSpan,
RouteParams: routeParamSpans(fragment.Route, fragment.RouteSpan.Start.Line, rawLine),
})
- page.Blocks.Spans.Fragments = append(page.Blocks.Spans.Fragments, manifest.NamedSpan{Name: fragment.Name, Span: fragment.Span})
+ page.Blocks.Spans.Fragments = append(page.Blocks.Spans.Fragments, source.NamedSpan{Name: fragment.Name, Span: fragment.Span})
}
if page.ID == "" {
@@ -79,7 +80,7 @@ func lowerPageSyntax(source []byte, ast gwdkast.File, defaultID string) (manifes
return page, nil
}
-func lowerPageSyntaxAnnotations(source []byte, ast gwdkast.File, page *manifest.Page) error {
+func lowerPageSyntaxAnnotations(src []byte, ast gwdkast.File, page *manifest.Page) error {
if ast.Page != nil {
if ast.Page.ID == "" {
return fmt.Errorf("line %d: @page requires a value", ast.Page.Span.Start.Line)
@@ -110,32 +111,32 @@ func lowerPageSyntaxAnnotations(source []byte, ast gwdkast.File, page *manifest.
}
for _, layout := range ast.Layouts {
page.Layouts = append(page.Layouts, layout.ID)
- page.Spans.Layouts = append(page.Spans.Layouts, manifest.NamedSpan{Name: layout.ID, Span: layout.Span})
+ page.Spans.Layouts = append(page.Spans.Layouts, source.NamedSpan{Name: layout.ID, Span: layout.Span})
}
for _, guard := range ast.Guards {
page.Guard = append(page.Guard, guard.Name)
- page.Spans.Guard = append(page.Spans.Guard, manifest.NamedSpan{Name: guard.Name, Span: guard.Span})
+ page.Spans.Guard = append(page.Spans.Guard, source.NamedSpan{Name: guard.Name, Span: guard.Span})
}
for _, css := range ast.CSS {
page.CSS = append(page.CSS, css.Path)
- page.Spans.CSS = append(page.Spans.CSS, manifest.NamedSpan{Name: css.Path, Span: css.Span})
+ page.Spans.CSS = append(page.Spans.CSS, source.NamedSpan{Name: css.Path, Span: css.Span})
}
for _, script := range ast.JS {
if strings.TrimSpace(script.Path) != "" {
page.JS = append(page.JS, script.Path)
- page.Spans.JS = append(page.Spans.JS, manifest.NamedSpan{Name: script.Path, Span: script.Span})
+ page.Spans.JS = append(page.Spans.JS, source.NamedSpan{Name: script.Path, Span: script.Span})
continue
}
- name := manifest.InlineScriptName(len(page.InlineJS))
- page.InlineJS = append(page.InlineJS, manifest.InlineScript{Name: name, Body: script.Inline, Span: script.Span})
- page.Spans.InlineJS = append(page.Spans.InlineJS, manifest.NamedSpan{Name: name, Span: script.Span})
+ name := source.InlineScriptName(len(page.InlineJS))
+ page.InlineJS = append(page.InlineJS, source.InlineScript{Name: name, Body: script.Inline, Span: script.Span})
+ page.Spans.InlineJS = append(page.Spans.InlineJS, source.NamedSpan{Name: name, Span: script.Span})
}
for _, annotation := range ast.Annotations {
if pageAnnotationLoweredFromAST(ast, annotation.Name) {
continue
}
lineNumber := annotation.Span.Start.Line
- rawLine := sourceLineText(source, lineNumber)
+ rawLine := sourceLineText(src, lineNumber)
if err := applyAnnotation(page, annotation.Name, annotation.Value, lineNumber, rawLine); err != nil {
return fmt.Errorf("line %d: %w", lineNumber, err)
}
@@ -195,22 +196,22 @@ func lowerSyntaxStores(in []gwdkast.Store) []manifest.Store {
return out
}
-func lowerSyntaxRouteParams(in []gwdkast.RouteParam) []manifest.RouteParam {
- out := make([]manifest.RouteParam, 0, len(in))
+func lowerSyntaxRouteParams(in []gwdkast.RouteParam) []source.RouteParam {
+ out := make([]source.RouteParam, 0, len(in))
for _, param := range in {
paramType := param.Type
if paramType == "" {
paramType = "string"
}
- out = append(out, manifest.RouteParam{Name: param.Name, Type: paramType, Span: param.Span})
+ out = append(out, source.RouteParam{Name: param.Name, Type: paramType, Span: param.Span})
}
return out
}
-func lowerSyntaxRouteParamSpans(in []gwdkast.RouteParam) []manifest.NamedSpan {
- out := make([]manifest.NamedSpan, 0, len(in))
+func lowerSyntaxRouteParamSpans(in []gwdkast.RouteParam) []source.NamedSpan {
+ out := make([]source.NamedSpan, 0, len(in))
for _, param := range in {
- out = append(out, manifest.NamedSpan{Name: param.Name, Span: param.Span})
+ out = append(out, source.NamedSpan{Name: param.Name, Span: param.Span})
}
return out
}
@@ -239,7 +240,7 @@ func applyPageSyntaxBlock(page *manifest.Page, block gwdkast.Block) {
Body: block.Body,
Span: block.Span,
})
- page.Blocks.Spans.GoBlocks = append(page.Blocks.Spans.GoBlocks, manifest.NamedSpan{Name: block.Name, Span: block.Span})
+ page.Blocks.Spans.GoBlocks = append(page.Blocks.Spans.GoBlocks, source.NamedSpan{Name: block.Name, Span: block.Span})
case "view":
page.Blocks.View = true
page.Blocks.ViewBody = block.Body
@@ -251,11 +252,11 @@ func applyPageSyntaxBlock(page *manifest.Page, block gwdkast.Block) {
}
}
-func sourceLineText(source []byte, lineNumber int) string {
+func sourceLineText(src []byte, lineNumber int) string {
if lineNumber <= 0 {
return ""
}
- lines := strings.Split(string(source), "\n")
+ lines := strings.Split(string(src), "\n")
if lineNumber > len(lines) {
return ""
}
diff --git a/internal/parser/route_helpers.go b/internal/parser/route_helpers.go
index a603bb6..93289c5 100644
--- a/internal/parser/route_helpers.go
+++ b/internal/parser/route_helpers.go
@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
- "github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
)
func splitList(value string) []string {
@@ -32,7 +32,7 @@ func splitCSSList(value string) []string {
return out
}
-func sourceLineSpan(lineNumber int, rawLine string) manifest.SourceSpan {
+func sourceLineSpan(lineNumber int, rawLine string) source.SourceSpan {
startColumn := 1
for _, r := range rawLine {
if r != ' ' && r != '\t' {
@@ -44,29 +44,29 @@ func sourceLineSpan(lineNumber int, rawLine string) manifest.SourceSpan {
if endColumn <= startColumn {
endColumn = startColumn + 1
}
- return manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: lineNumber, Column: startColumn},
- End: manifest.SourcePosition{Line: lineNumber, Column: endColumn},
+ return source.SourceSpan{
+ Start: source.SourcePosition{Line: lineNumber, Column: startColumn},
+ End: source.SourcePosition{Line: lineNumber, Column: endColumn},
}
}
-func sourceBodyStart(lines []string, firstLineNumber int) manifest.SourcePosition {
+func sourceBodyStart(lines []string, firstLineNumber int) source.SourcePosition {
for offset, rawLine := range lines {
for index, char := range []rune(rawLine) {
if strings.TrimSpace(string(char)) == "" {
continue
}
- return manifest.SourcePosition{Line: firstLineNumber + offset, Column: index + 1}
+ return source.SourcePosition{Line: firstLineNumber + offset, Column: index + 1}
}
}
- return manifest.SourcePosition{}
+ return source.SourcePosition{}
}
-func namedValueSpans(values []string, lineNumber int, rawLine string) []manifest.NamedSpan {
+func namedValueSpans(values []string, lineNumber int, rawLine string) []source.NamedSpan {
if len(values) == 0 {
return nil
}
- spans := make([]manifest.NamedSpan, 0, len(values))
+ spans := make([]source.NamedSpan, 0, len(values))
searchStart := 0
for _, value := range values {
if value == "" {
@@ -74,16 +74,16 @@ func namedValueSpans(values []string, lineNumber int, rawLine string) []manifest
}
index := strings.Index(rawLine[searchStart:], value)
if index < 0 {
- spans = append(spans, manifest.NamedSpan{Name: value, Span: sourceLineSpan(lineNumber, rawLine)})
+ spans = append(spans, source.NamedSpan{Name: value, Span: sourceLineSpan(lineNumber, rawLine)})
continue
}
start := searchStart + index
end := start + len([]rune(value))
- spans = append(spans, manifest.NamedSpan{
+ spans = append(spans, source.NamedSpan{
Name: value,
- Span: manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: lineNumber, Column: start + 1},
- End: manifest.SourcePosition{Line: lineNumber, Column: end + 1},
+ Span: source.SourceSpan{
+ Start: source.SourcePosition{Line: lineNumber, Column: start + 1},
+ End: source.SourcePosition{Line: lineNumber, Column: end + 1},
},
})
searchStart = end
@@ -91,7 +91,7 @@ func namedValueSpans(values []string, lineNumber int, rawLine string) []manifest
return spans
}
-func parseRouteDeclaration(route string, lineNumber int, rawLine string) (string, []manifest.RouteParam, []manifest.NamedSpan, error) {
+func parseRouteDeclaration(route string, lineNumber int, rawLine string) (string, []source.RouteParam, []source.NamedSpan, error) {
matches := routeParamPattern.FindAllStringSubmatchIndex(route, -1)
if len(matches) == 0 {
return route, nil, nil, nil
@@ -102,8 +102,8 @@ func parseRouteDeclaration(route string, lineNumber int, rawLine string) (string
}
normalizedParts := make([]string, 0, len(matches)*3+1)
last := 0
- params := make([]manifest.RouteParam, 0, len(matches))
- spans := make([]manifest.NamedSpan, 0, len(matches))
+ params := make([]source.RouteParam, 0, len(matches))
+ spans := make([]source.NamedSpan, 0, len(matches))
for _, match := range matches {
name := route[match[2]:match[3]]
paramType := "string"
@@ -115,12 +115,12 @@ func parseRouteDeclaration(route string, lineNumber int, rawLine string) (string
}
start := routeStart + match[0]
end := routeStart + match[1]
- span := manifest.SourceSpan{
- Start: manifest.SourcePosition{Line: lineNumber, Column: start + 1},
- End: manifest.SourcePosition{Line: lineNumber, Column: end + 1},
+ span := source.SourceSpan{
+ Start: source.SourcePosition{Line: lineNumber, Column: start + 1},
+ End: source.SourcePosition{Line: lineNumber, Column: end + 1},
}
- params = append(params, manifest.RouteParam{Name: name, Type: paramType, Span: span})
- spans = append(spans, manifest.NamedSpan{
+ params = append(params, source.RouteParam{Name: name, Type: paramType, Span: span})
+ spans = append(spans, source.NamedSpan{
Name: name,
Span: span,
})
@@ -131,7 +131,7 @@ func parseRouteDeclaration(route string, lineNumber int, rawLine string) (string
return strings.Join(normalizedParts, ""), params, spans, nil
}
-func routeParamSpans(route string, lineNumber int, rawLine string) []manifest.NamedSpan {
+func routeParamSpans(route string, lineNumber int, rawLine string) []source.NamedSpan {
_, _, spans, _ := parseRouteDeclaration(route, lineNumber, rawLine)
return spans
}
diff --git a/internal/parser/syntax.go b/internal/parser/syntax.go
index 0624905..8ae8580 100644
--- a/internal/parser/syntax.go
+++ b/internal/parser/syntax.go
@@ -9,6 +9,7 @@ import (
"github.com/cssbruno/gowdk/internal/cssscope"
"github.com/cssbruno/gowdk/internal/gwdkast"
"github.com/cssbruno/gowdk/internal/manifest"
+ "github.com/cssbruno/gowdk/internal/source"
"github.com/cssbruno/gowdk/internal/view"
)
@@ -35,16 +36,16 @@ type APIStatement = gwdkast.APIStatement
// ParseSyntax parses a .gwdk source file into a typed syntax AST for the
// current compiler subset.
-func ParseSyntax(source []byte) (SyntaxFile, error) {
+func ParseSyntax(src []byte) (SyntaxFile, error) {
var file SyntaxFile
var body []syntaxBodyLine
var captured SyntaxBlock
var capturedFragment *SyntaxFragmentEndpoint
depth := 0
seenDeclaration := false
- seenGoBlocks := map[string]manifest.SourceSpan{}
+ seenGoBlocks := map[string]source.SourceSpan{}
- scanner := bufio.NewScanner(bytes.NewReader(source))
+ scanner := bufio.NewScanner(bytes.NewReader(src))
for lineNumber := 1; scanner.Scan(); lineNumber++ {
rawLine := scanner.Text()
line := strings.TrimSpace(rawLine)
@@ -478,16 +479,16 @@ func finishSyntaxBlock(block SyntaxBlock, body []syntaxBodyLine) (SyntaxBlock, e
return block, nil
}
-func syntaxBodyStart(body []syntaxBodyLine) manifest.SourcePosition {
+func syntaxBodyStart(body []syntaxBodyLine) source.SourcePosition {
for _, raw := range body {
for index, char := range []rune(raw.Text) {
if strings.TrimSpace(string(char)) == "" {
continue
}
- return manifest.SourcePosition{Line: raw.Line, Column: index + 1}
+ return source.SourcePosition{Line: raw.Line, Column: index + 1}
}
}
- return manifest.SourcePosition{}
+ return source.SourcePosition{}
}
func parseBuildCall(body []syntaxBodyLine) (BuildCall, bool, error) {
diff --git a/internal/source/source.go b/internal/source/source.go
new file mode 100644
index 0000000..c2a3494
--- /dev/null
+++ b/internal/source/source.go
@@ -0,0 +1,90 @@
+// Package source holds the neutral leaf value types shared across the GOWDK
+// compiler packages: source spans, route params, inline scripts, and backend
+// binding metadata. These types carry no behavior and depend on nothing else in
+// the module, so every layer (parser, AST, IR, manifest, generated output) can
+// reference them without creating import cycles or coupling to the manifest
+// page/component model.
+//
+// Historically these lived in internal/manifest, which forced packages that
+// only needed a SourceSpan to depend on the whole manifest model (and made
+// internal/gwdkir depend on manifest). They were extracted here so the
+// manifest model and the IR can both reference shared leaf types from a neutral
+// home. manifest re-exports them as aliases for backward compatibility.
+package source
+
+import "fmt"
+
+// SourcePosition is a 1-based source location in a parsed .gwdk file.
+type SourcePosition struct {
+ Line int
+ Column int
+}
+
+// SourceSpan is a 1-based source range. End is exclusive.
+type SourceSpan struct {
+ Start SourcePosition
+ End SourcePosition
+}
+
+// NamedSpan records the source range for a named declaration or reference.
+type NamedSpan struct {
+ Name string
+ Span SourceSpan
+}
+
+// RouteParam describes one dynamic route parameter and its declared scalar
+// type. Empty Type means string for compatibility with legacy {name} syntax.
+type RouteParam struct {
+ Name string
+ Type string
+ Span SourceSpan
+}
+
+// InlineScript records browser module code declared directly inside a .gwdk
+// source file. Path-based script declarations should remain preferred.
+type InlineScript struct {
+ Name string
+ Body string
+ Span SourceSpan
+}
+
+// InlineScriptName returns the deterministic generated filename for the
+// zero-based inline browser script declaration index in one source owner.
+func InlineScriptName(index int) string {
+ if index <= 0 {
+ return "inline-gowdk.js"
+ }
+ return fmt.Sprintf("inline-%d-gowdk.js", index+1)
+}
+
+// BackendBindingStatus describes whether a .gwdk backend block has a matching
+// same-package Go handler.
+type BackendBindingStatus string
+
+const (
+ BackendBindingBound BackendBindingStatus = "bound"
+ BackendBindingMissing BackendBindingStatus = "missing"
+ BackendBindingUnsupportedSignature BackendBindingStatus = "unsupported_signature"
+)
+
+// BackendSignatureKind describes the supported Go handler shape.
+type BackendSignatureKind string
+
+const (
+ BackendSignatureAction0 BackendSignatureKind = "action0"
+ BackendSignatureActionValues BackendSignatureKind = "action_values"
+ BackendSignatureActionForm BackendSignatureKind = "action_form"
+ BackendSignatureActionFormPtr BackendSignatureKind = "action_form_ptr"
+ BackendSignatureAPI BackendSignatureKind = "api"
+ BackendSignatureFragment BackendSignatureKind = "fragment"
+ BackendSignatureLoad BackendSignatureKind = "load"
+ BackendSignatureLoadError BackendSignatureKind = "load_error"
+)
+
+// BackendInputField describes one form field decoded into a Go action input
+// struct from compile-time Go AST metadata.
+type BackendInputField struct {
+ FieldName string
+ FormName string
+ Type string
+}