@greenpandastudios/aug-cli 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/THIRD_PARTY_NOTICES.md +4 -0
- package/bin/aug.mjs +12 -2
- package/docs/about.md +27 -0
- package/docs/api/crypto.md +103 -69
- package/docs/api/io.md +35 -33
- package/docs/api/json.md +3 -3
- package/docs/api/memory.md +23 -19
- package/docs/api/time.md +10 -9
- package/docs/api/web.md +33 -38
- package/docs/assets/benchmarks/execution.svg +774 -637
- package/docs/assets/benchmarks/http.svg +278 -249
- package/docs/assets/benchmarks/improvements.svg +66 -66
- package/docs/assets/benchmarks/kernels-mobile.svg +1 -0
- package/docs/assets/benchmarks/kernels.svg +1 -0
- package/docs/assets/benchmarks/memory.svg +153 -153
- package/docs/benchmark-results.json +227089 -90363
- package/docs/compatibility.md +8 -8
- package/docs/contributing-benchmarks.md +68 -0
- package/docs/dev-containers.md +88 -0
- package/docs/dgx-consumers.json +122 -0
- package/docs/dgx-gyms.json +784 -0
- package/docs/dgx-kernels.json +1170 -0
- package/docs/dgx-performance.json +227849 -0
- package/docs/dgx-spark.md +64 -0
- package/docs/diagnostics.md +15 -5
- package/docs/docker.md +173 -12
- package/docs/editor.md +35 -0
- package/docs/example-projects.json +238 -24
- package/docs/examples/approved-design/counters.md +34 -126
- package/docs/examples/approved-design/dependencies/august/0.21.0/io/contracts.md +178 -0
- package/docs/examples/approved-design/domain/app.md +13 -77
- package/docs/examples/approved-design/domain/export.md +5 -19
- package/docs/examples/approved-design/domain/models.md +4 -23
- package/docs/examples/approved-design/domain/numbers.md +24 -114
- package/docs/examples/approved-design/index.md +16 -7
- package/docs/examples/approved-design/main.md +19 -96
- package/docs/examples/benchmark/index.md +6 -5
- package/docs/examples/benchmark/main.md +7 -35
- package/docs/examples/calls-benchmark/index.md +36 -0
- package/docs/examples/calls-benchmark/main.md +78 -0
- package/docs/examples/calls-benchmark/operations.md +60 -0
- package/docs/examples/cli-args/index.md +6 -5
- package/docs/examples/cli-args/main.md +6 -31
- package/docs/examples/collections/index.md +6 -5
- package/docs/examples/collections/main.md +7 -35
- package/docs/examples/collections-benchmark/index.md +6 -5
- package/docs/examples/collections-benchmark/main.md +7 -35
- package/docs/examples/cpu-benchmark/index.md +6 -5
- package/docs/examples/cpu-benchmark/main.md +5 -24
- package/docs/examples/developer-workflow/calculator.md +39 -186
- package/docs/examples/developer-workflow/dependencies/august/0.21.0/io/contracts.md +178 -0
- package/docs/examples/developer-workflow/index.md +16 -7
- package/docs/examples/developer-workflow/logging/console.md +10 -56
- package/docs/examples/developer-workflow/logging/export.md +4 -14
- package/docs/examples/developer-workflow/logging/logger.md +7 -43
- package/docs/examples/developer-workflow/main.md +17 -69
- package/docs/examples/drop/index.md +6 -5
- package/docs/examples/drop/main.md +7 -27
- package/docs/examples/drop/resource.md +7 -30
- package/docs/examples/errors/errors.md +7 -27
- package/docs/examples/errors/index.md +6 -5
- package/docs/examples/errors/main.md +7 -28
- package/docs/examples/errors-benchmark/index.md +36 -0
- package/docs/examples/errors-benchmark/main.md +92 -0
- package/docs/examples/errors-benchmark/operations.md +65 -0
- package/docs/examples/ffi/index.md +6 -5
- package/docs/examples/ffi/main.md +6 -20
- package/docs/examples/ffi/native.md +8 -34
- package/docs/examples/float-benchmark/index.md +35 -0
- package/docs/examples/float-benchmark/main.md +75 -0
- package/docs/examples/generic-di/dependencies/august/0.21.0/io/contracts.md +178 -0
- package/docs/examples/generic-di/index.md +6 -5
- package/docs/examples/generic-di/main.md +9 -39
- package/docs/examples/generic-di/types.md +23 -92
- package/docs/examples/generics/index.md +6 -5
- package/docs/examples/generics/main.md +10 -47
- package/docs/examples/generics/types.md +25 -100
- package/docs/examples/hello/app/export.md +4 -12
- package/docs/examples/hello/app/greeter.md +13 -91
- package/docs/examples/hello/dependencies/august/0.21.0/io/contracts.md +178 -0
- package/docs/examples/hello/index.md +17 -6
- package/docs/examples/hello/logging/console.md +10 -54
- package/docs/examples/hello/logging/export.md +4 -14
- package/docs/examples/hello/logging/logger.md +7 -45
- package/docs/examples/hello/main.md +9 -39
- package/docs/examples/http-benchmark/index.md +6 -5
- package/docs/examples/http-benchmark/main.md +6 -20
- package/docs/examples/http-benchmark/routes.md +9 -31
- package/docs/examples/index.md +63 -29
- package/docs/examples/interceptors/app.md +20 -139
- package/docs/examples/interceptors/dependencies/august/0.21.0/io/contracts.md +178 -0
- package/docs/examples/interceptors/index.md +6 -5
- package/docs/examples/interceptors/interceptors.md +24 -148
- package/docs/examples/interceptors/logging.md +13 -76
- package/docs/examples/interceptors/main.md +10 -65
- package/docs/examples/json-benchmark/data.md +4 -20
- package/docs/examples/json-benchmark/dependencies/packages/@git/url_2d3c37c690c0fa115be1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.md +67 -0
- package/docs/examples/json-benchmark/index.md +7 -5
- package/docs/examples/json-benchmark/main-yaml.md +20 -0
- package/docs/examples/json-benchmark/main.md +10 -49
- package/docs/examples/list-benchmark/index.md +35 -0
- package/docs/examples/list-benchmark/main.md +78 -0
- package/docs/examples/map-churn-benchmark/index.md +35 -0
- package/docs/examples/map-churn-benchmark/main.md +106 -0
- package/docs/examples/native-blake3/dependencies/packages/@greenpandastudios/aug-blake3/0.1.3/api.md +104 -0
- package/docs/examples/native-blake3/dependencies/packages/@greenpandastudios/aug-blake3/0.1.3/contracts.md +54 -0
- package/docs/examples/native-blake3/dependencies/packages/@greenpandastudios/aug-blake3/0.1.3/native.abi-json.md +48 -0
- package/docs/examples/native-blake3/hashing.md +93 -0
- package/docs/examples/native-blake3/index.md +38 -0
- package/docs/examples/native-blake3/main.md +73 -0
- package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/api.md +406 -0
- package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/bindings.md +51 -0
- package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/contracts.md +63 -0
- package/docs/examples/native-pytorch/dependencies/packages/@greenpandastudios/aug-pytorch/0.1.4/native.abi-json.md +161 -0
- package/docs/examples/native-pytorch/index.md +38 -0
- package/docs/examples/native-pytorch/main.md +73 -0
- package/docs/examples/native-pytorch/tensors.md +123 -0
- package/docs/examples/native-sqlite/database.md +116 -0
- package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/api.md +284 -0
- package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/bindings.md +51 -0
- package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/contracts.md +75 -0
- package/docs/examples/native-sqlite/dependencies/packages/@greenpandastudios/aug-sqlite/0.1.3/native.abi-json.md +119 -0
- package/docs/examples/native-sqlite/index.md +38 -0
- package/docs/examples/native-sqlite/main.md +73 -0
- package/docs/examples/native-zlib/compression.md +99 -0
- package/docs/examples/native-zlib/dependencies/packages/@greenpandastudios/aug-zlib/0.1.3/api.md +153 -0
- package/docs/examples/native-zlib/dependencies/packages/@greenpandastudios/aug-zlib/0.1.3/contracts.md +54 -0
- package/docs/examples/native-zlib/dependencies/packages/@greenpandastudios/aug-zlib/0.1.3/native.abi-json.md +76 -0
- package/docs/examples/native-zlib/index.md +38 -0
- package/docs/examples/native-zlib/main.md +78 -0
- package/docs/examples/new-syntax/console.md +10 -52
- package/docs/examples/new-syntax/dependencies/august/0.21.0/io/contracts.md +178 -0
- package/docs/examples/new-syntax/greeter.md +13 -71
- package/docs/examples/new-syntax/index.md +6 -5
- package/docs/examples/new-syntax/logger.md +7 -43
- package/docs/examples/new-syntax/main.md +10 -53
- package/docs/examples/new-syntax/math.md +6 -24
- package/docs/examples/oidc-login/client/contracts.md +10 -61
- package/docs/examples/oidc-login/client/endpoints.md +32 -154
- package/docs/examples/oidc-login/client/export.md +5 -23
- package/docs/examples/oidc-login/client/login.md +234 -344
- package/docs/examples/oidc-login/client/logout.md +38 -143
- package/docs/examples/oidc-login/client/protocol.md +183 -304
- package/docs/examples/oidc-login/client/session.md +41 -149
- package/docs/examples/oidc-login/client/views.md +13 -58
- package/docs/examples/oidc-login/common/export.md +6 -26
- package/docs/examples/oidc-login/common/headers.md +42 -72
- package/docs/examples/oidc-login/common/keys.md +40 -164
- package/docs/examples/oidc-login/common/settings.md +24 -40
- package/docs/examples/oidc-login/common/views.md +7 -28
- package/docs/examples/oidc-login/dependencies/packages/@git/url_0eb7c89453c87681ed15/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/store.md +205 -0
- package/docs/examples/oidc-login/dependencies/packages/@git/url_2d3c37c690c0fa115be1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.md +67 -0
- package/docs/examples/oidc-login/dependencies/packages/@git/url_897efafd565158fc4908/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.md +267 -0
- package/docs/examples/oidc-login/dependencies/packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40/contracts.md +415 -0
- package/docs/examples/oidc-login/dependencies/packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40/jose.md +271 -0
- package/docs/examples/oidc-login/dependencies/packages/@git/url_c092cd151499c4e1d8a1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.md +89 -0
- package/docs/examples/oidc-login/index.md +8 -7
- package/docs/examples/oidc-login/main-yaml.md +7 -0
- package/docs/examples/oidc-login/main.md +26 -153
- package/docs/examples/oidc-login/provider/authorization.md +232 -275
- package/docs/examples/oidc-login/provider/contracts.md +24 -146
- package/docs/examples/oidc-login/provider/credentials.md +24 -57
- package/docs/examples/oidc-login/provider/discovery.md +48 -122
- package/docs/examples/oidc-login/provider/export.md +11 -37
- package/docs/examples/oidc-login/provider/token.md +159 -237
- package/docs/examples/oidc-login/provider/userinfo.md +47 -102
- package/docs/examples/oidc-login/provider/views.md +13 -53
- package/docs/examples/ownership/counter.md +17 -63
- package/docs/examples/ownership/index.md +6 -5
- package/docs/examples/ownership/main.md +7 -30
- package/docs/examples/ownership-transfer/dependencies/august/0.21.0/io/contracts.md +178 -0
- package/docs/examples/ownership-transfer/index.md +6 -5
- package/docs/examples/ownership-transfer/main.md +10 -52
- package/docs/examples/ownership-transfer/resource.md +15 -67
- package/docs/examples/packages-app/dependencies/packages/@example/aug-math/0.1.0/arithmetic.md +12 -50
- package/docs/examples/packages-app/index.md +7 -6
- package/docs/examples/packages-app/main.md +7 -25
- package/docs/examples/packages-math/aug-package-json.md +1 -1
- package/docs/examples/packages-math/index.md +7 -6
- package/docs/examples/packages-math/src/arithmetic.md +12 -50
- package/docs/examples/packages-math/src/export.md +4 -12
- package/docs/examples/records-benchmark/data.md +55 -0
- package/docs/examples/records-benchmark/index.md +36 -0
- package/docs/examples/records-benchmark/main.md +85 -0
- package/docs/examples/startup-benchmark/index.md +6 -5
- package/docs/examples/startup-benchmark/main.md +5 -17
- package/docs/examples/strings-benchmark/index.md +35 -0
- package/docs/examples/strings-benchmark/main.md +76 -0
- package/docs/examples/tasks-benchmark/index.md +36 -0
- package/docs/examples/tasks-benchmark/main.md +89 -0
- package/docs/examples/tasks-benchmark/operations.md +58 -0
- package/docs/examples/visibility/counter.md +19 -67
- package/docs/examples/visibility/index.md +6 -5
- package/docs/examples/visibility/main.md +7 -31
- package/docs/examples/weather-api/forecasts.md +168 -0
- package/docs/examples/weather-api/index.md +42 -0
- package/docs/examples/weather-api/main-yaml.md +21 -0
- package/docs/examples/weather-api/main.md +65 -0
- package/docs/examples.json +10 -0
- package/docs/getting-started.md +105 -2
- package/docs/grammar.md +7 -5
- package/docs/guides/change-a-module.md +64 -0
- package/docs/guides/index.md +27 -0
- package/docs/gym-results.json +784 -0
- package/docs/implementation-map.md +6 -0
- package/docs/index.md +51 -29
- package/docs/kernel-results.json +1170 -0
- package/docs/language-conformance.md +7 -1
- package/docs/language-constructs.md +30 -22
- package/docs/language-design-audit.md +1 -1
- package/docs/learn/data-and-errors.md +77 -0
- package/docs/learn/index.md +30 -0
- package/docs/learn/modules-and-dependencies.md +73 -0
- package/docs/learn/state-and-tests.md +71 -0
- package/docs/learn/values-and-functions.md +62 -0
- package/docs/lesson-failures.json +8 -0
- package/docs/maintaining-docs.md +16 -4
- package/docs/native-implementation.md +276 -0
- package/docs/native-interop-llvm-plan.md +342 -0
- package/docs/native-package-examples.md +684 -0
- package/docs/native-packages.md +179 -0
- package/docs/packages.md +72 -126
- package/docs/performance.md +61 -68
- package/docs/production-readiness.md +16 -5
- package/docs/public/downloads/approved-design.zip +0 -0
- package/docs/public/downloads/benchmark.zip +0 -0
- package/docs/public/downloads/calls-benchmark.zip +0 -0
- package/docs/public/downloads/cli-args.zip +0 -0
- package/docs/public/downloads/collections-benchmark.zip +0 -0
- package/docs/public/downloads/collections.zip +0 -0
- package/docs/public/downloads/cpu-benchmark.zip +0 -0
- package/docs/public/downloads/developer-workflow.zip +0 -0
- package/docs/public/downloads/drop.zip +0 -0
- package/docs/public/downloads/errors-benchmark.zip +0 -0
- package/docs/public/downloads/errors.zip +0 -0
- package/docs/public/downloads/ffi.zip +0 -0
- package/docs/public/downloads/float-benchmark.zip +0 -0
- package/docs/public/downloads/generic-di.zip +0 -0
- package/docs/public/downloads/generics.zip +0 -0
- package/docs/public/downloads/hello.zip +0 -0
- package/docs/public/downloads/http-benchmark.zip +0 -0
- package/docs/public/downloads/interceptors.zip +0 -0
- package/docs/public/downloads/json-benchmark.zip +0 -0
- package/docs/public/downloads/list-benchmark.zip +0 -0
- package/docs/public/downloads/map-churn-benchmark.zip +0 -0
- package/docs/public/downloads/native-blake3.zip +0 -0
- package/docs/public/downloads/native-pytorch.zip +0 -0
- package/docs/public/downloads/native-sqlite.zip +0 -0
- package/docs/public/downloads/native-zlib.zip +0 -0
- package/docs/public/downloads/new-syntax.zip +0 -0
- package/docs/public/downloads/oidc-login.zip +0 -0
- package/docs/public/downloads/ownership-transfer.zip +0 -0
- package/docs/public/downloads/ownership.zip +0 -0
- package/docs/public/downloads/packages-app.zip +0 -0
- package/docs/public/downloads/packages-math.zip +0 -0
- package/docs/public/downloads/records-benchmark.zip +0 -0
- package/docs/public/downloads/startup-benchmark.zip +0 -0
- package/docs/public/downloads/strings-benchmark.zip +0 -0
- package/docs/public/downloads/tasks-benchmark.zip +0 -0
- package/docs/public/downloads/visibility.zip +0 -0
- package/docs/public/downloads/weather-api.zip +0 -0
- package/docs/qualification-results.md +66 -0
- package/docs/reference.md +47 -17
- package/docs/releasing.md +105 -16
- package/docs/research/code-to-natural-language.md +108 -0
- package/docs/research/ecosystem-workflow.md +41 -0
- package/docs/research/libtorch-native-qualification.md +115 -0
- package/docs/research/native-interop-llvm.md +197 -0
- package/docs/research/qualification-methods.md +15 -0
- package/docs/research/wiki-editorial-design.md +71 -0
- package/docs/roadmap.md +2 -1
- package/docs/safety-gyms.md +41 -0
- package/docs/specifications.md +27 -11
- package/docs/testing.md +19 -5
- package/docs/tooling.md +36 -22
- package/docs/weather-api.md +65 -0
- package/docs/web-library-gaps.md +1 -1
- package/docs/web.md +54 -18
- package/docs/writing-docs.md +49 -0
- package/examples/{hello/.aug-spec/august/0.19.0 → approved-design/.aug-spec/august/0.21.0}/io/contracts.aug +4 -3
- package/examples/approved-design/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
- package/examples/approved-design/.aug-spec/manifest.json +3 -3
- package/examples/approved-design/counters.aug +5 -4
- package/examples/approved-design/counters.aug.md +25 -132
- package/examples/approved-design/domain/app.aug +2 -1
- package/examples/approved-design/domain/app.aug.md +10 -80
- package/examples/approved-design/domain/export.aug +1 -0
- package/examples/approved-design/domain/export.aug.md +4 -20
- package/examples/approved-design/domain/models.aug +1 -0
- package/examples/approved-design/domain/models.aug.md +3 -25
- package/examples/approved-design/domain/numbers.aug +3 -2
- package/examples/approved-design/domain/numbers.aug.md +19 -116
- package/examples/approved-design/main.aug +1 -0
- package/examples/approved-design/main.aug.md +12 -95
- package/examples/benchmark/.aug-spec/manifest.json +1 -1
- package/examples/benchmark/main.aug +1 -0
- package/examples/benchmark/main.aug.md +6 -36
- package/examples/cli-args/.aug-spec/manifest.json +1 -1
- package/examples/cli-args/main.aug +1 -0
- package/examples/cli-args/main.aug.md +5 -32
- package/examples/collections/.aug-spec/manifest.json +1 -1
- package/examples/collections/main.aug +1 -0
- package/examples/collections/main.aug.md +6 -36
- package/examples/{generic-di/.aug-spec/august/0.19.0 → developer-workflow/.aug-spec/august/0.21.0}/io/contracts.aug +4 -3
- package/examples/developer-workflow/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
- package/examples/developer-workflow/.aug-spec/manifest.json +3 -3
- package/examples/developer-workflow/calculator.aug +3 -2
- package/examples/developer-workflow/calculator.aug.md +28 -189
- package/examples/developer-workflow/logging/console.aug +2 -1
- package/examples/developer-workflow/logging/console.aug.md +7 -57
- package/examples/developer-workflow/logging/export.aug +1 -0
- package/examples/developer-workflow/logging/export.aug.md +3 -15
- package/examples/developer-workflow/logging/logger.aug +1 -0
- package/examples/developer-workflow/logging/logger.aug.md +6 -46
- package/examples/developer-workflow/main.aug +1 -0
- package/examples/developer-workflow/main.aug.md +10 -68
- package/examples/drop/.aug-spec/manifest.json +1 -1
- package/examples/drop/main.aug +1 -0
- package/examples/drop/main.aug.md +6 -28
- package/examples/drop/resource.aug +1 -0
- package/examples/drop/resource.aug.md +6 -34
- package/examples/errors/.aug-spec/manifest.json +1 -1
- package/examples/errors/errors.aug +2 -1
- package/examples/errors/errors.aug.md +4 -27
- package/examples/errors/main.aug +1 -0
- package/examples/errors/main.aug.md +6 -29
- package/examples/ffi/.aug-spec/manifest.json +1 -1
- package/examples/ffi/main.aug +1 -0
- package/examples/ffi/main.aug.md +5 -21
- package/examples/ffi/native.aug +2 -1
- package/examples/ffi/native.aug.md +5 -35
- package/examples/{approved-design/.aug-spec/august/0.19.0 → generic-di/.aug-spec/august/0.21.0}/io/contracts.aug +4 -3
- package/examples/generic-di/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
- package/examples/generic-di/.aug-spec/manifest.json +3 -3
- package/examples/generic-di/main.aug +1 -0
- package/examples/generic-di/main.aug.md +8 -40
- package/examples/generic-di/types.aug +3 -2
- package/examples/generic-di/types.aug.md +18 -97
- package/examples/generics/.aug-spec/manifest.json +1 -1
- package/examples/generics/main.aug +1 -0
- package/examples/generics/main.aug.md +9 -48
- package/examples/generics/types.aug +4 -3
- package/examples/generics/types.aug.md +18 -104
- package/examples/{developer-workflow/.aug-spec/august/0.19.0 → hello/.aug-spec/august/0.21.0}/io/contracts.aug +4 -3
- package/examples/hello/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
- package/examples/hello/.aug-spec/manifest.json +3 -3
- package/examples/hello/app/export.aug +1 -0
- package/examples/hello/app/export.aug.md +3 -13
- package/examples/hello/app/greeter.aug +2 -1
- package/examples/hello/app/greeter.aug.md +10 -94
- package/examples/hello/logging/console.aug +2 -1
- package/examples/hello/logging/console.aug.md +7 -55
- package/examples/hello/logging/export.aug +1 -0
- package/examples/hello/logging/export.aug.md +3 -15
- package/examples/hello/logging/logger.aug +1 -0
- package/examples/hello/logging/logger.aug.md +6 -48
- package/examples/hello/main.aug +1 -0
- package/examples/hello/main.aug.md +8 -40
- package/examples/interceptors/.aug-spec/august/0.21.0/io/contracts.aug +37 -0
- package/examples/interceptors/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
- package/examples/interceptors/.aug-spec/manifest.json +3 -3
- package/examples/interceptors/app.aug +3 -2
- package/examples/interceptors/app.aug.md +15 -141
- package/examples/interceptors/interceptors.aug +3 -2
- package/examples/interceptors/interceptors.aug.md +19 -152
- package/examples/interceptors/logging.aug +2 -1
- package/examples/interceptors/logging.aug.md +10 -79
- package/examples/interceptors/main.aug +1 -0
- package/examples/interceptors/main.aug.md +9 -66
- package/examples/native-blake3/.aug-spec/manifest.json +16 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.1/native.abi.json +29 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/api.aug +14 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/api.aug.md +34 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/contracts.aug +4 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/contracts.aug.md +8 -0
- package/examples/native-blake3/.aug-spec/packages/@greenpandastudios/aug-blake3/0.1.3/native.abi.json +29 -0
- package/examples/native-blake3/AGENTS.md +5 -0
- package/examples/native-blake3/aug.lock.json +253 -0
- package/examples/native-blake3/hashing.aug +11 -0
- package/examples/native-blake3/hashing.aug.md +25 -0
- package/examples/native-blake3/main.aug +8 -0
- package/examples/native-blake3/main.aug.md +13 -0
- package/examples/native-pytorch/.aug-spec/manifest.json +18 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.1/native.abi.json +142 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.3/native.abi.json +142 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/api.aug +101 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/api.aug.md +160 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/bindings.aug +3 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/bindings.aug.md +8 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/contracts.aug +6 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/contracts.aug.md +13 -0
- package/examples/native-pytorch/.aug-spec/packages/@greenpandastudios/aug-pytorch/0.1.4/native.abi.json +142 -0
- package/examples/native-pytorch/AGENTS.md +5 -0
- package/examples/native-pytorch/aug.lock.json +309 -0
- package/examples/native-pytorch/main.aug +8 -0
- package/examples/native-pytorch/main.aug.md +13 -0
- package/examples/native-pytorch/tensors.aug +23 -0
- package/examples/native-pytorch/tensors.aug.md +35 -0
- package/examples/native-sqlite/.aug-spec/manifest.json +18 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.2/native.abi.json +100 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/api.aug +48 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/api.aug.md +91 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/bindings.aug +3 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/bindings.aug.md +8 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/contracts.aug +8 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/contracts.aug.md +22 -0
- package/examples/native-sqlite/.aug-spec/packages/@greenpandastudios/aug-sqlite/0.1.3/native.abi.json +100 -0
- package/examples/native-sqlite/AGENTS.md +5 -0
- package/examples/native-sqlite/aug.lock.json +236 -0
- package/examples/native-sqlite/database.aug +15 -0
- package/examples/native-sqlite/database.aug.md +27 -0
- package/examples/native-sqlite/main.aug +8 -0
- package/examples/native-sqlite/main.aug.md +13 -0
- package/examples/native-zlib/.aug-spec/manifest.json +16 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.1/native.abi.json +57 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/api.aug +29 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/api.aug.md +54 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/contracts.aug +4 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/contracts.aug.md +8 -0
- package/examples/native-zlib/.aug-spec/packages/@greenpandastudios/aug-zlib/0.1.3/native.abi.json +57 -0
- package/examples/native-zlib/AGENTS.md +5 -0
- package/examples/native-zlib/aug.lock.json +236 -0
- package/examples/native-zlib/compression.aug +15 -0
- package/examples/native-zlib/compression.aug.md +27 -0
- package/examples/native-zlib/main.aug +10 -0
- package/examples/native-zlib/main.aug.md +13 -0
- package/examples/new-syntax/.aug-spec/august/0.21.0/io/contracts.aug +37 -0
- package/examples/new-syntax/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
- package/examples/new-syntax/.aug-spec/manifest.json +3 -3
- package/examples/new-syntax/console.aug +2 -1
- package/examples/new-syntax/console.aug.md +7 -53
- package/examples/new-syntax/greeter.aug +2 -1
- package/examples/new-syntax/greeter.aug.md +10 -74
- package/examples/new-syntax/logger.aug +1 -0
- package/examples/new-syntax/logger.aug.md +6 -46
- package/examples/new-syntax/main.aug +1 -0
- package/examples/new-syntax/main.aug.md +9 -54
- package/examples/new-syntax/math.aug +2 -1
- package/examples/new-syntax/math.aug.md +3 -24
- package/examples/oidc-login/.aug-spec/manifest.json +13 -13
- package/examples/oidc-login/.aug-spec/packages/@git/url_0eb7c89453c87681ed15/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/store.aug.md +71 -0
- package/examples/oidc-login/.aug-spec/packages/@git/url_2d3c37c690c0fa115be1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.aug.md +17 -0
- package/examples/oidc-login/.aug-spec/packages/@git/url_897efafd565158fc4908/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.aug.md +125 -0
- package/examples/oidc-login/.aug-spec/{august/0.19.0/crypto → packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40}/contracts.aug +12 -11
- package/examples/oidc-login/.aug-spec/packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40/contracts.aug.md +244 -0
- package/examples/oidc-login/.aug-spec/{august/0.19.0/crypto → packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40}/jose.aug +6 -5
- package/examples/oidc-login/.aug-spec/packages/@git/url_9ef654c66d34ab8f5527/0.0.0-git.b14a0f9aa41f1ce58bd51133bcdc424033e40d40/jose.aug.md +71 -0
- package/examples/oidc-login/.aug-spec/packages/@git/url_c092cd151499c4e1d8a1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301/contracts.aug.md +30 -0
- package/examples/oidc-login/aug.lock.json +100 -0
- package/examples/oidc-login/client/contracts.aug +1 -0
- package/examples/oidc-login/client/contracts.aug.md +9 -66
- package/examples/oidc-login/client/endpoints.aug +6 -5
- package/examples/oidc-login/client/endpoints.aug.md +13 -145
- package/examples/oidc-login/client/export.aug +1 -0
- package/examples/oidc-login/client/export.aug.md +4 -24
- package/examples/oidc-login/client/login.aug +7 -6
- package/examples/oidc-login/client/login.aug.md +25 -303
- package/examples/oidc-login/client/logout.aug +5 -4
- package/examples/oidc-login/client/logout.aug.md +13 -135
- package/examples/oidc-login/client/protocol.aug +7 -6
- package/examples/oidc-login/client/protocol.aug.md +36 -283
- package/examples/oidc-login/client/session.aug +5 -4
- package/examples/oidc-login/client/session.aug.md +12 -139
- package/examples/oidc-login/client/views.aug +3 -2
- package/examples/oidc-login/client/views.aug.md +8 -57
- package/examples/oidc-login/common/export.aug +1 -0
- package/examples/oidc-login/common/export.aug.md +5 -27
- package/examples/oidc-login/common/headers.aug +4 -3
- package/examples/oidc-login/common/headers.aug.md +9 -67
- package/examples/oidc-login/common/keys.aug +6 -5
- package/examples/oidc-login/common/keys.aug.md +29 -165
- package/examples/oidc-login/common/settings.aug +2 -1
- package/examples/oidc-login/common/settings.aug.md +5 -39
- package/examples/oidc-login/common/views.aug +2 -1
- package/examples/oidc-login/common/views.aug.md +4 -28
- package/examples/oidc-login/main.aug +6 -16
- package/examples/oidc-login/main.aug.md +17 -146
- package/examples/oidc-login/main.yaml +7 -0
- package/examples/oidc-login/provider/authorization.aug +7 -6
- package/examples/oidc-login/provider/authorization.aug.md +23 -238
- package/examples/oidc-login/provider/contracts.aug +1 -0
- package/examples/oidc-login/provider/contracts.aug.md +23 -158
- package/examples/oidc-login/provider/credentials.aug +3 -2
- package/examples/oidc-login/provider/credentials.aug.md +9 -53
- package/examples/oidc-login/provider/discovery.aug +4 -3
- package/examples/oidc-login/provider/discovery.aug.md +11 -118
- package/examples/oidc-login/provider/export.aug +1 -0
- package/examples/oidc-login/provider/export.aug.md +6 -34
- package/examples/oidc-login/provider/token.aug +6 -5
- package/examples/oidc-login/provider/token.aug.md +16 -204
- package/examples/oidc-login/provider/userinfo.aug +4 -3
- package/examples/oidc-login/provider/userinfo.aug.md +10 -92
- package/examples/oidc-login/provider/views.aug +3 -2
- package/examples/oidc-login/provider/views.aug.md +8 -52
- package/examples/ownership/.aug-spec/manifest.json +1 -1
- package/examples/ownership/counter.aug +3 -2
- package/examples/ownership/counter.aug.md +12 -66
- package/examples/ownership/main.aug +1 -0
- package/examples/ownership/main.aug.md +6 -31
- package/examples/ownership-transfer/.aug-spec/august/0.21.0/io/contracts.aug +37 -0
- package/examples/ownership-transfer/.aug-spec/august/0.21.0/io/contracts.aug.md +82 -0
- package/examples/ownership-transfer/.aug-spec/manifest.json +3 -3
- package/examples/ownership-transfer/main.aug +1 -0
- package/examples/ownership-transfer/main.aug.md +9 -53
- package/examples/ownership-transfer/resource.aug +2 -1
- package/examples/ownership-transfer/resource.aug.md +12 -71
- package/examples/packages/app/.aug-spec/manifest.json +1 -1
- package/examples/packages/app/.aug-spec/packages/@example/aug-math/0.1.0/arithmetic.aug +2 -1
- package/examples/packages/app/.aug-spec/packages/@example/aug-math/0.1.0/arithmetic.aug.md +9 -51
- package/examples/packages/app/main.aug +1 -0
- package/examples/packages/app/main.aug.md +6 -26
- package/examples/packages/math/.aug-spec/manifest.json +1 -1
- package/examples/packages/math/aug-package.json +1 -1
- package/examples/packages/math/src/arithmetic.aug +2 -1
- package/examples/packages/math/src/arithmetic.aug.md +9 -51
- package/examples/packages/math/src/export.aug +1 -0
- package/examples/packages/math/src/export.aug.md +3 -13
- package/examples/visibility/.aug-spec/manifest.json +1 -1
- package/examples/visibility/counter.aug +4 -3
- package/examples/visibility/counter.aug.md +12 -68
- package/examples/visibility/main.aug +1 -0
- package/examples/visibility/main.aug.md +6 -32
- package/examples/weather-api/.aug-spec/manifest.json +8 -0
- package/examples/weather-api/AGENTS.md +17 -0
- package/examples/weather-api/README.md +19 -0
- package/examples/weather-api/forecasts.aug +47 -0
- package/examples/weather-api/forecasts.aug.md +32 -0
- package/examples/weather-api/main.aug +4 -0
- package/examples/weather-api/main.aug.md +15 -0
- package/examples/weather-api/main.yaml +5 -0
- package/examples/weather-api/weather.http +5 -0
- package/native/compiler-packs.json +43 -0
- package/package.json +4 -4
- package/runtime/aug_http.c +48 -21
- package/runtime/aug_http_ir.c +30 -0
- package/runtime/aug_http_ir.h +18 -0
- package/runtime/aug_ir.c +209 -0
- package/runtime/aug_ir.h +89 -0
- package/runtime/aug_json.c +6 -1
- package/runtime/aug_runtime.c +82 -16
- package/runtime/aug_runtime.h +16 -2
- package/runtime/aug_tasks.c +38 -7
- package/runtime/aug_values.c +12 -6
- package/scripts/bootstrap-native.mjs +184 -106
- package/scripts/native-setup.mjs +98 -0
- package/scripts/native-toolchain.mjs +28 -0
- package/src/ast.js +1 -1
- package/src/builtins.js +1 -1
- package/src/checker.js +369 -100
- package/src/cli.js +198 -26
- package/src/codegen.js +32 -7
- package/src/compiler-packs.js +66 -0
- package/src/config.js +3 -3
- package/src/contracts.js +10 -0
- package/src/editor.js +86 -22
- package/src/fixes.js +90 -2
- package/src/formatter.js +23 -10
- package/src/git-http.js +137 -0
- package/src/git-packages.js +118 -0
- package/src/help.js +33 -24
- package/src/http-contracts.js +14 -0
- package/src/http-policies.js +5 -5
- package/src/ir-types.js +5 -0
- package/src/ir-verify.js +277 -0
- package/src/ir.js +962 -0
- package/src/libraries.js +1 -1
- package/src/llvm-debug.js +101 -0
- package/src/llvm-native.js +152 -0
- package/src/llvm-platform.js +20 -0
- package/src/llvm.js +781 -0
- package/src/lsp.js +14 -5
- package/src/native-artifacts.js +207 -0
- package/src/native-bindings.js +189 -0
- package/src/native-contracts.js +295 -0
- package/src/native-declarations.js +68 -0
- package/src/native-facts.js +63 -0
- package/src/native.js +19 -13
- package/src/navigation.js +4 -2
- package/src/openapi.js +5 -4
- package/src/package-locking.js +88 -0
- package/src/package-manager.js +267 -68
- package/src/parser.js +32 -6
- package/src/policies.js +3 -3
- package/src/project-init.js +78 -4
- package/src/project.js +11 -9
- package/src/runtime-abi.js +23 -0
- package/src/runtime-adapters.js +18 -0
- package/src/schemas.js +24 -17
- package/src/semantic.js +54 -5
- package/src/snippets.js +60 -0
- package/src/spec-hints.js +56 -0
- package/src/spec-tree.js +241 -0
- package/src/spec.js +669 -305
- package/docs/examples/approved-design/dependencies/august/0.19.0/io/contracts.md +0 -395
- package/docs/examples/developer-workflow/dependencies/august/0.19.0/io/contracts.md +0 -395
- package/docs/examples/generic-di/dependencies/august/0.19.0/io/contracts.md +0 -395
- package/docs/examples/hello/dependencies/august/0.19.0/io/contracts.md +0 -395
- package/docs/examples/interceptors/dependencies/august/0.19.0/io/contracts.md +0 -395
- package/docs/examples/json-benchmark/dependencies/august/0.19.0/json/contracts.md +0 -103
- package/docs/examples/new-syntax/dependencies/august/0.19.0/io/contracts.md +0 -395
- package/docs/examples/oidc-login/dependencies/august/0.19.0/crypto/contracts.md +0 -925
- package/docs/examples/oidc-login/dependencies/august/0.19.0/crypto/jose.md +0 -434
- package/docs/examples/oidc-login/dependencies/august/0.19.0/json/contracts.md +0 -103
- package/docs/examples/oidc-login/dependencies/august/0.19.0/memory/store.md +0 -374
- package/docs/examples/oidc-login/dependencies/august/0.19.0/time/contracts.md +0 -150
- package/docs/examples/oidc-login/dependencies/august/0.19.0/web/contracts.md +0 -532
- package/docs/examples/ownership-transfer/dependencies/august/0.19.0/io/contracts.md +0 -395
- package/examples/approved-design/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
- package/examples/developer-workflow/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
- package/examples/generic-di/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
- package/examples/hello/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
- package/examples/interceptors/.aug-spec/august/0.19.0/io/contracts.aug +0 -36
- package/examples/interceptors/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
- package/examples/new-syntax/.aug-spec/august/0.19.0/io/contracts.aug +0 -36
- package/examples/new-syntax/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
- package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/contracts.aug.md +0 -791
- package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/jose.aug.md +0 -266
- package/examples/oidc-login/.aug-spec/august/0.19.0/json/contracts.aug.md +0 -55
- package/examples/oidc-login/.aug-spec/august/0.19.0/memory/store.aug.md +0 -250
- package/examples/oidc-login/.aug-spec/august/0.19.0/time/contracts.aug.md +0 -96
- package/examples/oidc-login/.aug-spec/august/0.19.0/web/contracts.aug.md +0 -420
- package/examples/ownership-transfer/.aug-spec/august/0.19.0/io/contracts.aug +0 -36
- package/examples/ownership-transfer/.aug-spec/august/0.19.0/io/contracts.aug.md +0 -316
- /package/examples/oidc-login/.aug-spec/{august/0.19.0/memory → packages/@git/url_0eb7c89453c87681ed15/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301}/store.aug +0 -0
- /package/examples/oidc-login/.aug-spec/{august/0.19.0/json → packages/@git/url_2d3c37c690c0fa115be1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301}/contracts.aug +0 -0
- /package/examples/oidc-login/.aug-spec/{august/0.19.0/web → packages/@git/url_897efafd565158fc4908/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301}/contracts.aug +0 -0
- /package/examples/oidc-login/.aug-spec/{august/0.19.0/time → packages/@git/url_c092cd151499c4e1d8a1/0.0.0-git.a39fc582565d4fca40be4f75fa304d71adc69301}/contracts.aug +0 -0
package/src/spec.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
import { callableResult, callableErrors } from "./contracts.js";
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
4
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
4
5
|
import { fieldsOf, typeName } from "./ast.js";
|
|
5
|
-
import { builtinFunctions, builtinProperties, collectionOperations, operationType } from "./builtins.js";
|
|
6
|
+
import { builtinFunctions, builtinProperties, builtinTypes, collectionOperations, operationType } from "./builtins.js";
|
|
6
7
|
import { callableDocumentation } from "./documentation.js";
|
|
7
8
|
import { javadocBefore } from "./javadoc.js";
|
|
8
9
|
import { libraryChild, libraryRelative } from "./libraries.js";
|
|
9
10
|
import { compilerVersion } from "./package-manager.js";
|
|
10
11
|
import { tyName } from "./types.js";
|
|
12
|
+
import { action, attempt, branch, choice, coordinate, flow, loop, paragraph, renderSpecTree, scope, section, sequence, step } from "./spec-tree.js";
|
|
13
|
+
import { specHint } from "./spec-hints.js";
|
|
14
|
+
import { nativeFact, nativeDescription } from "./native-facts.js";
|
|
11
15
|
const generated = '<!-- Generated by aug spec. Edit the August source, then regenerate. -->';
|
|
12
16
|
const copied = '// Generated by aug spec. This is a copy of the installed dependency source.\n';
|
|
13
17
|
const code = (value) => { const text = value.replaceAll('\n', '\\n'); const fence = '`'.repeat(1 + Math.max(0, ...(text.match(/`+/g) ?? []).map(part => part.length))); return fence + (text.startsWith('`') ? ' ' : '') + text + (text.endsWith('`') ? ' ' : '') + fence; };
|
|
@@ -16,13 +20,15 @@ const anchor = (name) => 'symbol-' + name;
|
|
|
16
20
|
const hash = (text) => createHash('sha256').update(text).digest('hex');
|
|
17
21
|
const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
18
22
|
const unreachable = (node) => { throw new Error(`No specification renderer for ${node.kind}`); };
|
|
19
|
-
|
|
23
|
+
const plain = (text) => text.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replaceAll('`', '');
|
|
24
|
+
/** Plan prose, managed source pointers, and versioned dependency copies without writes or execution. */
|
|
20
25
|
export function generateSpecs(checked, options = {}) {
|
|
21
26
|
if (checked.diagnostics.some(issue => issue.severity !== 'warning'))
|
|
22
27
|
throw new Error('Fix compiler errors before generating specifications');
|
|
23
28
|
const project = checked.project;
|
|
24
29
|
const owned = options.files ?? [...project.files.values()].filter(file => !file.builtin && !file.package);
|
|
25
30
|
const own = new Set(owned.map(file => file.path));
|
|
31
|
+
const hints = owned.map(specHint), offsets = new Map(hints.map(hint => [hint.file.path, hint.lineOffset]));
|
|
26
32
|
const docs = new Map(), sources = new Map();
|
|
27
33
|
for (const file of project.files.values()) {
|
|
28
34
|
if (own.has(file.path)) {
|
|
@@ -38,13 +44,20 @@ export function generateSpecs(checked, options = {}) {
|
|
|
38
44
|
sources.set(file.path, source);
|
|
39
45
|
}
|
|
40
46
|
const queue = [...owned].sort((a, b) => compare(a.path, b.path));
|
|
41
|
-
const seen = new Set(), outputs =
|
|
47
|
+
const seen = new Set(), outputs = hints.map(hint => ({ path: hint.file.path, text: hint.text, source: hint.file.path, kind: 'source-hint' }));
|
|
48
|
+
const contracts = new Map();
|
|
49
|
+
for (const [provider, path] of checked.native.providerDescriptors) {
|
|
50
|
+
const at = provider.lastIndexOf('@'), name = provider.slice(0, at), version = provider.slice(at + 1);
|
|
51
|
+
const destination = join(project.root, '.aug-spec', 'packages', name, version, 'native.abi.json');
|
|
52
|
+
contracts.set(provider, destination);
|
|
53
|
+
outputs.push({ path: destination, text: readFileSync(path, 'utf8'), source: path, kind: 'native-descriptor' });
|
|
54
|
+
}
|
|
42
55
|
for (let index = 0; index < queue.length; index++) {
|
|
43
56
|
const file = queue[index];
|
|
44
57
|
if (seen.has(file.path))
|
|
45
58
|
continue;
|
|
46
59
|
seen.add(file.path);
|
|
47
|
-
const writer = new SpecWriter(checked, file, docs, sources, own, path => {
|
|
60
|
+
const writer = new SpecWriter(checked, file, docs, sources, own, offsets, contracts, path => {
|
|
48
61
|
const dependency = project.files.get(path);
|
|
49
62
|
if (dependency && !seen.has(path))
|
|
50
63
|
queue.push(dependency);
|
|
@@ -61,19 +74,27 @@ class SpecWriter {
|
|
|
61
74
|
docs;
|
|
62
75
|
sources;
|
|
63
76
|
own;
|
|
77
|
+
offsets;
|
|
78
|
+
contracts;
|
|
64
79
|
enqueue;
|
|
65
80
|
used = new Map();
|
|
66
81
|
builtins = new Map();
|
|
67
82
|
properties = new Map();
|
|
68
83
|
locals = new Map();
|
|
69
|
-
constructor(checked, file, docs, sources, own, enqueue) {
|
|
84
|
+
constructor(checked, file, docs, sources, own, offsets, contracts, enqueue) {
|
|
70
85
|
this.checked = checked;
|
|
71
86
|
this.file = file;
|
|
72
87
|
this.docs = docs;
|
|
73
88
|
this.sources = sources;
|
|
74
89
|
this.own = own;
|
|
90
|
+
this.offsets = offsets;
|
|
91
|
+
this.contracts = contracts;
|
|
75
92
|
this.enqueue = enqueue;
|
|
76
93
|
}
|
|
94
|
+
nativeDescription(native) {
|
|
95
|
+
const contract = this.contracts.get(native.provider);
|
|
96
|
+
return nativeDescription(native, `[${code(native.descriptor)}](${url(relative(dirname(this.docs.get(this.file.path)), contract))})`);
|
|
97
|
+
}
|
|
77
98
|
definition(name) { return this.checked.project.scopes.get(this.file.path)?.get(name); }
|
|
78
99
|
use(def, method, field) {
|
|
79
100
|
if (!def || def.file === this.file.path)
|
|
@@ -99,95 +120,166 @@ class SpecWriter {
|
|
|
99
120
|
return `[${code(label)}](${url(relative(dirname(this.docs.get(this.file.path)), this.docs.get(def.file)))}#${encodeURIComponent(anchor(name))})`;
|
|
100
121
|
}
|
|
101
122
|
source(span) {
|
|
102
|
-
return `[source](${url(relative(dirname(this.docs.get(this.file.path)), this.sources.get(span.file)))}#L${span.line + (this.own.has(span.file) ? 0 : 1)})`;
|
|
123
|
+
return `[source](${url(relative(dirname(this.docs.get(this.file.path)), this.sources.get(span.file)))}#L${span.line + (this.own.has(span.file) ? this.offsets.get(span.file) ?? 0 : 1)})`;
|
|
103
124
|
}
|
|
104
|
-
heading(name, span, level
|
|
105
|
-
return
|
|
125
|
+
heading(name, span, level, children, role) {
|
|
126
|
+
return section(code(name) + (role ? ' · ' + role : ''), level, children, anchor(name), this.source(span));
|
|
106
127
|
}
|
|
107
128
|
type(type, file = this.file.path) {
|
|
108
|
-
const
|
|
129
|
+
const enclosing = this.checked.project.files.get(file)?.items.filter(item => 'typeParams' in item && item.span.start <= type.span.start && item.span.end >= type.span.end) ?? [];
|
|
130
|
+
const parameter = enclosing.some(item => 'typeParams' in item && (item.typeParams.includes(type.name) || 'methods' in item && item.methods.some(method => method.span.start <= type.span.start && method.span.end >= type.span.end && method.typeParams.includes(type.name))));
|
|
131
|
+
const def = parameter ? undefined : this.checked.project.scopes.get(file)?.get(type.name);
|
|
109
132
|
this.use(def);
|
|
110
133
|
type.args.forEach(arg => this.type(arg, file));
|
|
111
134
|
return def ? this.link(def, def.name, typeName(type)) : code(typeName(type));
|
|
112
135
|
}
|
|
113
|
-
notes(
|
|
114
|
-
|
|
115
|
-
|
|
136
|
+
notes(doc) {
|
|
137
|
+
if (!doc)
|
|
138
|
+
return '';
|
|
139
|
+
const prose = doc.markdown.split(/(?:^|\n\n)\*\*(?:Returns|Parameters|Throws|Deprecated:|See also:)/)[0].trim();
|
|
140
|
+
const extra = (doc.tags ?? []).filter(tag => tag.name === 'deprecated' || tag.name === 'see').map(tag => tag.name === 'deprecated' ? `Deprecated: ${tag.value}` : `See also: ${tag.value}`);
|
|
141
|
+
return [prose, ...extra].filter(Boolean).join(' ');
|
|
116
142
|
}
|
|
117
|
-
generics(node) {
|
|
118
|
-
return node.typeParams.length ? '
|
|
143
|
+
generics(node, file = this.file.path) {
|
|
144
|
+
return node.typeParams.length ? 'The type parameters are ' + coordinate(node.typeParams.map(name => code(name) +
|
|
119
145
|
(node.typeVariance?.[name] ? ` (${node.typeVariance[name] === 'out' ? 'produces values' : 'accepts values'})` : '') +
|
|
120
|
-
(node.typeConstraints?.[name]?.length ? ` must satisfy ${node.typeConstraints[name].map(type => this.type(type))
|
|
146
|
+
(node.typeConstraints?.[name]?.length ? ` which must satisfy ${coordinate(node.typeConstraints[name].map(type => this.type(type, file)))}` : ''))) + '.' : '';
|
|
121
147
|
}
|
|
122
|
-
inputs(params, fields = false, file = this.file.path) {
|
|
148
|
+
inputs(params, fields = false, file = this.file.path, descriptions = new Map()) {
|
|
123
149
|
if (!params.length)
|
|
124
150
|
return '';
|
|
125
|
-
|
|
151
|
+
if (fields && params.length === 1 && params[0].injected) {
|
|
152
|
+
const param = params[0];
|
|
126
153
|
this.locals.set(param.name, param.type);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
return 'The ' + code(param.name) + ' dependency is injected as ' + this.type(param.type, file) + ' and stored ' + (param.mutable ? 'mutably' : 'read-only') +
|
|
155
|
+
(param.name.startsWith('_') ? ' and privately' : '') + (param.ownership === 'own' ? ' with ownership transferred' : param.ownership === 'borrow' ? ' with permission to mutate it' : '') +
|
|
156
|
+
(descriptions.has(param.name) ? ' (' + descriptions.get(param.name).replace(/[.!?]$/, '') + ')' : '') + '.';
|
|
157
|
+
}
|
|
158
|
+
const describe = (group) => {
|
|
159
|
+
const param = group[0];
|
|
160
|
+
group.forEach(param => this.locals.set(param.name, param.type));
|
|
161
|
+
const name = coordinate(group.map(param => code(param.label ?? param.name))), type = this.type(param.type, file);
|
|
162
|
+
const simple = { int: group.length === 1 ? 'an integer' : 'integers', float: group.length === 1 ? 'a number' : 'numbers', string: group.length === 1 ? 'a string' : 'strings', bool: group.length === 1 ? 'a boolean' : 'booleans' };
|
|
163
|
+
let sentence = param.injected ? coordinate(group.map(param => code(param.name))) + ' (' + type + ')' : name + ' as ' + (simple[param.type.name] && !param.type.optional ? simple[param.type.name] : type);
|
|
164
|
+
if (param.source)
|
|
165
|
+
sentence += ` from the HTTP ${param.source.kind}${param.source.name ? ' ' + code(param.source.name) : ''}`;
|
|
134
166
|
if (param.ownership === 'own')
|
|
135
|
-
|
|
167
|
+
sentence += ' with ownership transferred';
|
|
136
168
|
else if (param.ownership === 'borrow')
|
|
137
|
-
|
|
169
|
+
sentence += ' with permission to mutate it during the call';
|
|
138
170
|
if (fields)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
171
|
+
sentence += `, kept ${param.mutable ? 'mutable' : 'read-only'}${param.name.startsWith('_') ? ' and private' : ''}${param.label && param.label !== param.name ? ' as ' + code(param.name) : ''}`;
|
|
172
|
+
const description = descriptions.get(param.name);
|
|
173
|
+
if (description)
|
|
174
|
+
sentence += ' (' + description.replace(/[.!?]$/, '').replace(/^The /, 'the ') + ')';
|
|
175
|
+
return sentence;
|
|
176
|
+
};
|
|
177
|
+
const groups = (injected) => this.parameterGroups(params.filter(param => param.injected === injected), fields, descriptions).map(describe);
|
|
178
|
+
const supplied = groups(false), injected = groups(true);
|
|
179
|
+
const queries = params.filter(param => !param.injected && param.source?.kind === 'query');
|
|
180
|
+
const onlyQueries = queries.length && queries.length === params.filter(param => !param.injected).length;
|
|
181
|
+
const aliases = queries.filter(param => param.source?.name && param.source.name !== param.name);
|
|
182
|
+
const typedQueries = queries.filter(param => param.type.name !== 'string');
|
|
183
|
+
const suppliedText = onlyQueries ? 'It reads ' + coordinate(queries.map(param => code(param.source?.name ?? param.label ?? param.name))) + ' from the HTTP query.' +
|
|
184
|
+
(aliases.length ? ' ' + coordinate(aliases.map(param => code(param.source.name) + ' is called ' + code(param.name) + ' here')) + '.' : '') +
|
|
185
|
+
(typedQueries.length ? ' It parses ' + coordinate(typedQueries.map(param => code(param.name) + ' as ' + this.type(param.type, file))) + '.' : '') :
|
|
186
|
+
supplied.length ? 'It takes ' + coordinate(supplied) + '.' : '';
|
|
187
|
+
return [suppliedText, injected.length ? 'It gets ' + coordinate(injected) + ' from dependency injection.' : '',
|
|
188
|
+
params.some(param => param.type.optional) ? 'Omitted optional inputs are null.' : ''].filter(Boolean).join(' ');
|
|
142
189
|
}
|
|
143
|
-
|
|
190
|
+
parameterGroups(params, fields = false, descriptions = new Map()) {
|
|
191
|
+
const groups = [];
|
|
192
|
+
const key = (value) => JSON.stringify([typeName(value.type), value.source, value.ownership, fields && value.mutable, fields && value.name.startsWith('_')]);
|
|
193
|
+
const alias = (value) => fields && value.label && value.label !== value.name;
|
|
194
|
+
for (const param of params) {
|
|
195
|
+
const previous = groups.at(-1)?.[0];
|
|
196
|
+
if (previous && key(previous) === key(param) && !descriptions.has(param.name) && !descriptions.has(previous.name) && !alias(param) && !alias(previous))
|
|
197
|
+
groups.at(-1).push(param);
|
|
198
|
+
else
|
|
199
|
+
groups.push([param]);
|
|
200
|
+
}
|
|
201
|
+
return groups;
|
|
202
|
+
}
|
|
203
|
+
contract(method, documentation) {
|
|
144
204
|
const effects = this.checked.effectContracts.get(method);
|
|
145
205
|
const layers = this.checked.interceptorPlans.get(method) ?? [];
|
|
146
|
-
const
|
|
206
|
+
for (const type of method.throws)
|
|
207
|
+
this.type(type, method.span.file);
|
|
208
|
+
const errors = callableErrors(this.checked, method);
|
|
209
|
+
for (const error of this.checked.callableContracts.get(method)?.errors ?? [])
|
|
210
|
+
this.use(error.def);
|
|
147
211
|
const changes = effects?.changes ?? method.changes ?? [];
|
|
148
212
|
const uses = [...(effects?.uses.values() ?? method.uses ?? [])];
|
|
149
|
-
|
|
150
|
-
|
|
213
|
+
const result = [];
|
|
214
|
+
const generics = this.generics(method), inputs = this.inputs(method.params, false, method.span.file, documentation?.parameters);
|
|
215
|
+
if (generics)
|
|
216
|
+
result.push(paragraph(generics));
|
|
217
|
+
if (inputs)
|
|
218
|
+
result.push(paragraph(inputs));
|
|
219
|
+
const facts = [];
|
|
220
|
+
const returnNote = documentation?.tags?.find(tag => tag.name === 'return' || tag.name === 'returns')?.value ??
|
|
221
|
+
documentation?.markdown.match(/\*\*Returns\*\* ([^\n]+)/)?.[1];
|
|
222
|
+
// Implementations explain their actual returns below. Contracts need the
|
|
223
|
+
// result type; repeating it before every return adds noise to the narrative.
|
|
224
|
+
const resultType = callableResult(this.checked, method);
|
|
225
|
+
const reference = (type) => ({ name: type.name, args: type.args.map(reference), nullable: type.nullable, optional: type.optional, span: method.returns.span });
|
|
226
|
+
const returns = reference(resultType);
|
|
227
|
+
this.use(resultType.def);
|
|
228
|
+
this.type(returns, method.span.file);
|
|
229
|
+
if (returns.name !== 'void' && (!method.body || returnNote || method.returnOwnership === 'own'))
|
|
230
|
+
facts.push(`It returns ${(method.returnOwnership === 'own' ? 'ownership of ' : '') + this.type(returns, method.span.file)}` +
|
|
231
|
+
(returnNote ? ` — ${returnNote.replace(/[.!?]$/, '')}` : '') + '.');
|
|
151
232
|
if (changes.length)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
233
|
+
facts.push('It may change ' + coordinate(changes.map(code)) + '.');
|
|
234
|
+
const capabilities = uses.map(effect => {
|
|
235
|
+
const def = 'capability' in effect ? effect.capability?.def : undefined;
|
|
236
|
+
const target = def ?? this.definition(effect.source);
|
|
237
|
+
const operation = target && 'methods' in target.node ? target.node.methods.find(method => method.name === effect.operation) : undefined;
|
|
238
|
+
this.use(target, operation);
|
|
239
|
+
return target ? this.link(target, operation ? `${target.name}.${operation.name}` : target.name, `${effect.source}.${effect.operation}`) : code(`${effect.source}.${effect.operation}`);
|
|
240
|
+
});
|
|
241
|
+
if (capabilities.length && !method.body)
|
|
242
|
+
facts.push('It can call ' + coordinate(capabilities) + '.');
|
|
243
|
+
if (errors.length && !method.endpoint)
|
|
244
|
+
facts.push('Failures can raise ' + coordinate(errors.map(name => {
|
|
245
|
+
const note = documentation?.tags?.find(tag => (tag.name === 'throws' || tag.name === 'exception') && tag.value.startsWith(name + ' '))?.value.slice(name.length).trim() ??
|
|
246
|
+
documentation?.markdown.split('**Throws**\n')[1]?.split('\n\n')[0]?.split('\n').find(line => line.startsWith('- ' + code(name) + ': '))?.slice(('- ' + code(name) + ': ').length);
|
|
247
|
+
const def = this.checked.project.scopes.get(method.span.file)?.get(name);
|
|
248
|
+
return (def ? this.link(def) : code(name)) + (note ? ` (${note.replace(/[.!?]$/, '').replace(/^When\b/, 'when')})` : '');
|
|
249
|
+
})) + '.');
|
|
250
|
+
if (facts.length)
|
|
251
|
+
result.push(paragraph(facts.join(' ')));
|
|
163
252
|
if (method.endpoint) {
|
|
164
253
|
const endpoint = method.endpoint;
|
|
165
|
-
|
|
166
|
-
(
|
|
167
|
-
|
|
254
|
+
if (endpoint.streams)
|
|
255
|
+
result.push(paragraph(`It streams ${this.type(method.returns)} items.`));
|
|
256
|
+
else if (endpoint.status !== 200)
|
|
257
|
+
result.push(paragraph(`A plain response body uses HTTP status ${endpoint.status}.`));
|
|
168
258
|
if (endpoint.errors.length)
|
|
169
|
-
|
|
259
|
+
result.push(paragraph('The handler responds with ' + coordinate(endpoint.errors.map(error => `HTTP ${error.status} for ${this.type(error.type)}`)) + '.'));
|
|
260
|
+
const unmapped = errors.filter(name => !endpoint.errors.some(error => typeName(error.type) === name));
|
|
261
|
+
if (unmapped.length)
|
|
262
|
+
result.push(paragraph('It can also raise ' + coordinate(unmapped.map(code)) + '.'));
|
|
170
263
|
}
|
|
171
|
-
return
|
|
264
|
+
return result;
|
|
172
265
|
}
|
|
173
266
|
layers(node) {
|
|
174
267
|
const layers = this.checked.interceptorPlans.get(node) ?? [];
|
|
175
268
|
const policies = node.kind === 'function' ? this.checked.httpPolicies.get(node) ?? [] : [];
|
|
176
269
|
if (!layers.length && !policies.length)
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
policies.forEach(
|
|
180
|
-
|
|
181
|
-
(node.kind === 'function' && policy.dependencies.length ? ' Use ' + policy.dependencies.map(index => code(node.params[index].name)).join(', ') + '.' : '')
|
|
270
|
+
return [];
|
|
271
|
+
const steps = [];
|
|
272
|
+
policies.forEach(policy => {
|
|
273
|
+
steps.push(this.policy(policy) +
|
|
274
|
+
(node.kind === 'function' && policy.dependencies.length ? ' Use ' + policy.dependencies.map(index => code(node.params[index].name)).join(', ') + '.' : ''));
|
|
182
275
|
});
|
|
183
|
-
layers.forEach(
|
|
276
|
+
layers.forEach(layer => {
|
|
184
277
|
this.use(layer.definition, layer.around);
|
|
185
|
-
|
|
186
|
-
'
|
|
187
|
-
(layer.annotation.mappings.length ? 'Map ' + layer.annotation.mappings.map(mapping => code(mapping.source) + ' to ' + code(mapping.name)).join(', ') + '. ' : '') +
|
|
188
|
-
'Unselected inputs pass through.\n';
|
|
278
|
+
steps.push(`Call ${this.link(layer.definition, `${layer.definition.name}.around`)}.` +
|
|
279
|
+
(layer.annotation.mappings.length ? ' Map ' + layer.annotation.mappings.map(mapping => code(mapping.source) + ' to ' + code(mapping.name)).join(', ') + '.' : ''));
|
|
189
280
|
});
|
|
190
|
-
return
|
|
281
|
+
return [paragraph('Layers run in the declared order. ' + steps.join(' ')),
|
|
282
|
+
...(policies.length ? [paragraph('HTTP policies run before decoding; custom interceptors run after it.')] : [])];
|
|
191
283
|
}
|
|
192
284
|
policy(policy) {
|
|
193
285
|
const options = policy.options;
|
|
@@ -202,10 +294,12 @@ class SpecWriter {
|
|
|
202
294
|
case 'Compress': return 'Negotiate gzip. Respect existing Content-Encoding and bodyless statuses. Encode stream items as complete concatenated gzip members.';
|
|
203
295
|
}
|
|
204
296
|
}
|
|
205
|
-
receiver(expr) {
|
|
206
|
-
const checked = this.checked.expressionTypes.get(expr)
|
|
207
|
-
if (checked)
|
|
208
|
-
return checked;
|
|
297
|
+
receiver(expr, operation) {
|
|
298
|
+
const checked = this.checked.expressionTypes.get(expr);
|
|
299
|
+
if (checked?.def)
|
|
300
|
+
return checked.def;
|
|
301
|
+
if (checked?.kind === 'param')
|
|
302
|
+
return checked.bounds?.find(bound => bound.def && (!operation || this.method(bound.def, operation)))?.def;
|
|
209
303
|
const ref = expr.kind === 'name' ? this.locals.get(expr.name) : undefined;
|
|
210
304
|
if (ref)
|
|
211
305
|
return this.definition(ref.name);
|
|
@@ -216,7 +310,7 @@ class SpecWriter {
|
|
|
216
310
|
if (def?.node.kind === 'class')
|
|
217
311
|
return def;
|
|
218
312
|
if (def?.node.kind === 'function')
|
|
219
|
-
return this.
|
|
313
|
+
return callableResult(this.checked, def.node).def;
|
|
220
314
|
}
|
|
221
315
|
return undefined;
|
|
222
316
|
}
|
|
@@ -246,57 +340,196 @@ class SpecWriter {
|
|
|
246
340
|
return def ? this.link(def) : code(expr.name);
|
|
247
341
|
}
|
|
248
342
|
case 'member': {
|
|
249
|
-
const def = this.receiver(expr.object);
|
|
343
|
+
const def = this.receiver(expr.object, expr.name);
|
|
250
344
|
this.use(def, undefined, expr.name);
|
|
251
345
|
const name = expr.name, receiverType = this.checked.expressionTypes.get(expr.object)?.name;
|
|
252
346
|
const property = receiverType && builtinProperties[receiverType]?.find(property => property.name === name);
|
|
253
347
|
if (property)
|
|
254
348
|
this.properties.set(receiverType + '.' + name, { ...property, type: this.checked.expressionTypes.get(expr) ? tyName(this.checked.expressionTypes.get(expr)) : property.type });
|
|
255
|
-
|
|
349
|
+
const object = this.expression(expr.object), path = this.memberPath(expr);
|
|
350
|
+
return path ? code(path) : `${code(name)} of ${object}`;
|
|
256
351
|
}
|
|
257
352
|
case 'unary': {
|
|
258
353
|
if (expr.op === '!' && expr.value.kind === 'binary' && (expr.value.op === '==' || expr.value.op === '!='))
|
|
259
354
|
return `${this.expression(expr.value.left, true)} ${expr.value.op === '==' ? 'does not equal' : 'equals'} ${this.expression(expr.value.right, true)}`;
|
|
355
|
+
if (expr.op === '!' && expr.value.kind === 'call')
|
|
356
|
+
return this.predicate(expr.value, false);
|
|
260
357
|
return `${expr.op === '!' ? 'not' : 'the negative of'} (${this.expression(expr.value)})`;
|
|
261
358
|
}
|
|
262
359
|
case 'binary': {
|
|
263
360
|
const words = { '+': 'plus', '-': 'minus', '*': 'times', '/': 'divided by', '==': 'equals', '!=': 'does not equal', '<': 'is less than', '>': 'is greater than', '<=': 'is at most', '>=': 'is at least', '&&': 'and', '||': 'or' };
|
|
264
361
|
if (!words[expr.op])
|
|
265
362
|
throw new Error(`No specification renderer for operator ${expr.op}`);
|
|
363
|
+
if (expr.right.kind === 'literal' && expr.right.value === null && ['==', '!='].includes(expr.op))
|
|
364
|
+
return this.expression(expr.left) + (expr.op === '==' ? ' is null' : ' is not null');
|
|
365
|
+
if (expr.right.kind === 'literal' && expr.right.value === 0 && ['>', '<'].includes(expr.op))
|
|
366
|
+
return this.expression(expr.left) + (expr.op === '>' ? ' is positive' : ' is negative');
|
|
266
367
|
const textParts = this.stringParts(expr);
|
|
267
|
-
if (expr.op === '+' && textParts.length >=
|
|
268
|
-
|
|
269
|
-
|
|
368
|
+
if (expr.op === '+' && textParts.length >= 2) {
|
|
369
|
+
const text = textParts.map(part => part.kind === 'literal' && typeof part.value === 'string' ?
|
|
370
|
+
part.value.replaceAll('{', '{{').replaceAll('}', '}}') : '{' + plain(this.expression(part)) + '}').join('');
|
|
371
|
+
return 'the text ' + code(text);
|
|
372
|
+
}
|
|
373
|
+
const child = (value) => this.expression(value, value.kind === 'binary' && !(['&&', '||'].includes(expr.op) &&
|
|
374
|
+
(value.op === expr.op || ['==', '!=', '<', '>', '<=', '>='].includes(value.op))));
|
|
375
|
+
const result = `${child(expr.left)} ${words[expr.op]} ${child(expr.right)}`;
|
|
270
376
|
return nested ? `(${result})` : result;
|
|
271
377
|
}
|
|
272
378
|
case 'collection': {
|
|
273
379
|
if (expr.collection === 'Map')
|
|
274
380
|
return 'a map with ' + expr.items.filter((_, index) => index % 2 === 0).map((key, index) => `${this.expression(key)} mapped to ${this.expression(expr.items[index * 2 + 1])}`).join('; ');
|
|
381
|
+
const first = expr.items[0];
|
|
382
|
+
if (expr.collection === 'List' && expr.items.length >= 3 && first?.kind === 'call' && first.callee.kind === 'name' && !this.locals.has(first.callee.name)) {
|
|
383
|
+
const name = first.callee.name, def = this.definition(name);
|
|
384
|
+
const labels = first.argLabels;
|
|
385
|
+
if (def?.node.kind === 'class' && def.node.record && labels.every(Boolean) && expr.items.every(item => item.kind === 'call' && item.callee.kind === 'name' && item.callee.name === name &&
|
|
386
|
+
item.typeArgs.map(typeName).join(',') === first.typeArgs.map(typeName).join(',') &&
|
|
387
|
+
item.args.every(arg => arg.kind === 'literal') && item.argLabels.length === labels.length &&
|
|
388
|
+
item.argLabels.every((label, index) => label === labels[index]))) {
|
|
389
|
+
this.call(first);
|
|
390
|
+
const rows = expr.items.map(item => code('(' + item.args.map(arg => arg.kind === 'literal' ? arg.numericText ?? JSON.stringify(arg.value) : '').join(', ') + ')'));
|
|
391
|
+
return `a list of ${expr.items.length} ${this.link(def)} records, with ${code('(' + labels.join(', ') + ')')} values of ${coordinate(rows)}, in that order`;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
275
394
|
return `a ${expr.collection === 'empty' ? 'context-typed empty collection' : expr.collection.toLowerCase()}` + (expr.items.length ? ' containing ' + expr.items.map(item => this.expression(item)).join(', ') : ' with no items');
|
|
276
395
|
}
|
|
277
396
|
case 'resolve': return `the instance provided for ${code(expr.name)}${expr.typeArgs.length ? ' with type arguments ' + expr.typeArgs.map(type => this.type(type)).join(', ') : ''}`;
|
|
278
|
-
case 'call':
|
|
279
|
-
|
|
280
|
-
|
|
397
|
+
case 'call': {
|
|
398
|
+
const action = this.call(expr);
|
|
399
|
+
const known = this.knownValue(expr);
|
|
400
|
+
if (known)
|
|
401
|
+
return known;
|
|
402
|
+
if (expr.callee.kind === 'member') {
|
|
403
|
+
const receiver = expr.callee.object, type = this.checked.expressionTypes.get(receiver)?.name;
|
|
404
|
+
if (!this.receiver(receiver) && ['string', 'Bytes', 'List', 'Set', 'Map', 'Tuple'].includes(type ?? '') && !expr.args.length) {
|
|
405
|
+
const value = this.expression(receiver);
|
|
406
|
+
if (expr.callee.name === 'bytes' && type === 'string')
|
|
407
|
+
return 'the UTF-8 bytes of ' + value;
|
|
408
|
+
if (expr.callee.name === 'base64url' && type === 'Bytes')
|
|
409
|
+
return 'the URL-safe base64 encoding of ' + value;
|
|
410
|
+
if (expr.callee.name === 'length')
|
|
411
|
+
return (type === 'string' || type === 'Bytes' ? 'the byte length of ' : 'the number of elements in ') + value;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const name = expr.callee.kind === 'name' ? expr.callee.name : '';
|
|
415
|
+
return action.startsWith('construct ') ? (/^[AEIOU]/i.test(name) ? 'an ' : 'a ') + action.slice('construct '.length) :
|
|
416
|
+
action.startsWith('call ') ? action.slice('call '.length) : action;
|
|
417
|
+
}
|
|
418
|
+
case 'start': return `a child task running ${this.expression(expr.call)} with its inputs captured now`;
|
|
419
|
+
case 'wait': return `the result of waiting for ${expr.tasks.map(task => this.expression(task)).join(' and ')}${expr.tasks.length > 1 ? ' in input order' : ''}; propagate failures`;
|
|
281
420
|
case 'handle': {
|
|
282
|
-
const plan = this.checked.actions.get(expr);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
421
|
+
const plan = this.checked.actions.get(expr), endpoint = plan?.endpoint.node.kind === 'function' ? plan.endpoint.node.endpoint : undefined;
|
|
422
|
+
const captures = expr.call.kind === 'call' ? expr.call.args.filter(value => value.kind !== 'formInput').map(value => this.expression(value)) : [];
|
|
423
|
+
return endpoint ? `a form action that sends ${code(endpoint.method + ' ' + endpoint.path)} to ${this.link(plan.endpoint)} on submission` +
|
|
424
|
+
(captures.length ? ' with captured ' + coordinate(captures) : '') : `a form action for ${this.expression(expr.call)}`;
|
|
286
425
|
}
|
|
287
|
-
case 'formInput': return 'the
|
|
426
|
+
case 'formInput': return 'the submitted form data';
|
|
288
427
|
case 'markupText': return code(expr.text);
|
|
289
428
|
case 'markup': {
|
|
290
429
|
const def = this.definition(expr.tag);
|
|
291
430
|
this.use(def);
|
|
292
|
-
|
|
431
|
+
const elements = { p: 'a paragraph', h1: 'a heading', h2: 'a heading', button: 'a button', a: 'a link' };
|
|
432
|
+
return (def ? 'the server component ' + this.link(def) : elements[expr.tag] ?? 'the HTML element ' + code(expr.tag || 'fragment')) +
|
|
293
433
|
(expr.attributes.length ? ' with ' + expr.attributes.map(attribute => `${code(attribute.name)} = ${this.expression(attribute.value)}`).join(', ') : '') +
|
|
294
434
|
(expr.children.length ? ' containing ' + expr.children.map(child => this.expression(child)).join(', ') : '') +
|
|
295
|
-
'
|
|
435
|
+
' with escaped text';
|
|
296
436
|
}
|
|
297
437
|
default: return unreachable(expr);
|
|
298
438
|
}
|
|
299
439
|
}
|
|
440
|
+
memberPath(expr) {
|
|
441
|
+
if (expr.kind === 'name' && !this.definition(expr.name))
|
|
442
|
+
return expr.name;
|
|
443
|
+
if (expr.kind === 'name' && this.locals.has(expr.name))
|
|
444
|
+
return expr.name;
|
|
445
|
+
if (expr.kind === 'member') {
|
|
446
|
+
const object = this.memberPath(expr.object);
|
|
447
|
+
return object ? object + '.' + expr.name : undefined;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
argument(expr, label) {
|
|
451
|
+
const explicit = expr.args.findIndex((arg, index) => (expr.argLabels[index] ?? (arg.kind === 'name' ? arg.name : '')) === label);
|
|
452
|
+
if (explicit >= 0)
|
|
453
|
+
return expr.args[explicit];
|
|
454
|
+
const plan = this.checked.callPlans.get(expr);
|
|
455
|
+
const def = expr.callee.kind === 'name' ? this.definition(expr.callee.name) : expr.callee.kind === 'member' ? this.receiver(expr.callee.object, expr.callee.name) : undefined;
|
|
456
|
+
const method = def?.node.kind === 'function' ? def.node : expr.callee.kind === 'member' && def ? this.method(def, expr.callee.name) : undefined;
|
|
457
|
+
const params = method?.params ?? (def?.node.kind === 'class' ? def.node.fields : []);
|
|
458
|
+
const slot = params.findIndex(param => (param.label ?? param.name) === label);
|
|
459
|
+
const index = slot >= 0 ? plan?.sourceIndices[slot] : undefined;
|
|
460
|
+
return index !== undefined && index >= 0 ? expr.args[index] : undefined;
|
|
461
|
+
}
|
|
462
|
+
/** Interpret only compiler built-ins and the canonical standard-library contracts. */
|
|
463
|
+
standardOperation(expr) {
|
|
464
|
+
if (expr.callee.kind !== 'member')
|
|
465
|
+
return;
|
|
466
|
+
const def = this.receiver(expr.callee.object, expr.callee.name);
|
|
467
|
+
if (!def || !this.checked.project.files.get(def.file)?.builtin)
|
|
468
|
+
return;
|
|
469
|
+
return libraryRelative(this.checked.project.libraries, def.file).replaceAll('\\', '/') + ':' + def.name + '.' + expr.callee.name;
|
|
470
|
+
}
|
|
471
|
+
knownValue(expr) {
|
|
472
|
+
const arg = (label) => { const value = this.argument(expr, label); return value ? this.expression(value) : undefined; };
|
|
473
|
+
const receiver = expr.callee.kind === 'member' ? this.expression(expr.callee.object) : '';
|
|
474
|
+
switch (this.standardOperation(expr)) {
|
|
475
|
+
case 'crypto/contracts.aug:Crypto.random':
|
|
476
|
+
case 'crypto/contracts.aug:GnuTlsCrypto.random':
|
|
477
|
+
return `${arg('size')} random bytes from ${receiver}`;
|
|
478
|
+
case 'crypto/contracts.aug:Crypto.decodeBase64url':
|
|
479
|
+
case 'crypto/contracts.aug:GnuTlsCrypto.decodeBase64url':
|
|
480
|
+
return `${arg('input')} decoded as URL-safe base64 by ${receiver}`;
|
|
481
|
+
case 'time/contracts.aug:Clock.now':
|
|
482
|
+
case 'time/contracts.aug:SystemClock.now': return 'the current time from ' + receiver;
|
|
483
|
+
case 'memory/store.aug:ExpiringStore.take':
|
|
484
|
+
case 'memory/store.aug:MemoryStore.take':
|
|
485
|
+
return `the live value removed from ${receiver} under ${arg('key')}, using ${arg('now')} as the current time`;
|
|
486
|
+
case 'memory/store.aug:ExpiringStore.get':
|
|
487
|
+
case 'memory/store.aug:MemoryStore.get':
|
|
488
|
+
return `the live value in ${receiver} under ${arg('key')}, using ${arg('now')} as the current time`;
|
|
489
|
+
}
|
|
490
|
+
if (expr.callee.kind === 'member' && !this.receiver(expr.callee.object)) {
|
|
491
|
+
const type = this.checked.expressionTypes.get(expr.callee.object)?.name;
|
|
492
|
+
if (expr.callee.name === 'get' && type === 'List')
|
|
493
|
+
return `the item at index ${arg('index')} in ${receiver}`;
|
|
494
|
+
if (expr.callee.name === 'get' && type === 'Map')
|
|
495
|
+
return `the value under ${arg('key')} in ${receiver}`;
|
|
496
|
+
if (expr.callee.name === 'contains' && type === 'Map')
|
|
497
|
+
return `whether ${receiver} contains the key ${arg('key')}`;
|
|
498
|
+
if (expr.callee.name === 'contains' && type === 'Set')
|
|
499
|
+
return `whether ${receiver} contains ${arg('value')}`;
|
|
500
|
+
if (expr.callee.name === 'with' && type === 'Headers')
|
|
501
|
+
return `${receiver} with the header ${arg('name')} set to ${arg('value')}`;
|
|
502
|
+
}
|
|
503
|
+
if (expr.callee.kind === 'name') {
|
|
504
|
+
const def = this.definition(expr.callee.name);
|
|
505
|
+
if (def?.file && this.checked.project.files.get(def.file)?.builtin && libraryRelative(this.checked.project.libraries, def.file).replaceAll('\\', '/') === 'web/contracts.aug' && def.name === 'urlEncode')
|
|
506
|
+
return 'URL-encoded ' + arg('input');
|
|
507
|
+
if (this.checked.expressionTypes.get(expr)?.name === 'Html') {
|
|
508
|
+
const message = this.argument(expr, 'message');
|
|
509
|
+
if (message && expr.args.length === 1)
|
|
510
|
+
return this.expression(expr.callee) + ' showing ' + this.expression(message);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (expr.callee.kind === 'name' && !this.definition(expr.callee.name) && expr.callee.name === 'HttpResponse') {
|
|
514
|
+
const body = arg('body'), status = arg('status') ?? '200', headers = arg('headers');
|
|
515
|
+
return `HTTP ${status.replaceAll('`', '')} with ${body ?? 'an empty body'}` + (headers ? ' and ' + headers + ' headers' : '');
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
predicate(expr, expected = true) {
|
|
519
|
+
this.call(expr);
|
|
520
|
+
if (expr.callee.kind === 'member' && !this.receiver(expr.callee.object) &&
|
|
521
|
+
this.checked.expressionTypes.get(expr.callee.object)?.name === 'string' && expr.callee.name === 'isToken') {
|
|
522
|
+
const min = this.argument(expr, 'min'), max = this.argument(expr, 'max');
|
|
523
|
+
return this.expression(expr.callee.object) + ` is ${expected ? '' : 'not '}a URL-safe ASCII token` +
|
|
524
|
+
(min && max ? ' with ' + this.expression(min) + ' to ' + this.expression(max) + ' characters' : '');
|
|
525
|
+
}
|
|
526
|
+
const operation = this.standardOperation(expr);
|
|
527
|
+
if (operation === 'crypto/contracts.aug:Crypto.equal' || operation === 'crypto/contracts.aug:GnuTlsCrypto.equal') {
|
|
528
|
+
const left = this.argument(expr, 'left'), right = this.argument(expr, 'right');
|
|
529
|
+
return this.expression(left) + ' and ' + this.expression(right) + (expected ? ' match' : ' differ') + ' when compared by ' + this.expression(expr.callee.kind === 'member' ? expr.callee.object : expr.callee);
|
|
530
|
+
}
|
|
531
|
+
return this.expression(expr) + ` returns ${expected ? 'true' : 'false'}`;
|
|
532
|
+
}
|
|
300
533
|
call(expr) {
|
|
301
534
|
if (expr.callee.kind === 'name' && ['List', 'Set', 'Map'].includes(expr.callee.name) && !this.definition(expr.callee.name)) {
|
|
302
535
|
const name = expr.callee.name, types = expr.typeArgs.map(type => this.type(type));
|
|
@@ -305,13 +538,14 @@ class SpecWriter {
|
|
|
305
538
|
name.toLowerCase() + ' of ' + (types[0] ?? 'any');
|
|
306
539
|
return items.length ? 'a ' + label + ' containing ' + items.join(', ') : 'an empty ' + label;
|
|
307
540
|
}
|
|
308
|
-
let target, method;
|
|
541
|
+
let target, method, constructs = false;
|
|
309
542
|
if (expr.callee.kind === 'name') {
|
|
310
543
|
const name = expr.callee.name;
|
|
311
544
|
const def = this.definition(expr.callee.name);
|
|
312
545
|
this.use(def);
|
|
313
546
|
if (def?.node.kind === 'class' && def.file !== this.file.path)
|
|
314
547
|
this.used.get(def.id).constructed = true;
|
|
548
|
+
constructs = def?.node.kind === 'class' || !def && Object.hasOwn(builtinTypes, name);
|
|
315
549
|
method = def?.node.kind === 'function' ? def.node : undefined;
|
|
316
550
|
const builtin = !def && builtinFunctions.find(operation => operation.name === name);
|
|
317
551
|
if (builtin)
|
|
@@ -320,7 +554,7 @@ class SpecWriter {
|
|
|
320
554
|
}
|
|
321
555
|
else if (expr.callee.kind === 'member') {
|
|
322
556
|
const name = expr.callee.name;
|
|
323
|
-
const def = this.receiver(expr.callee.object);
|
|
557
|
+
const def = this.receiver(expr.callee.object, expr.callee.name);
|
|
324
558
|
method = def && this.method(def, expr.callee.name);
|
|
325
559
|
this.use(def, method);
|
|
326
560
|
const receiver = this.checked.expressionTypes.get(expr.callee.object);
|
|
@@ -335,7 +569,10 @@ class SpecWriter {
|
|
|
335
569
|
// An inherited operation links to the interface that actually declares it.
|
|
336
570
|
const owner = method && [...this.checked.project.definitions.values()].find(def => 'methods' in def.node && def.node.methods.includes(method));
|
|
337
571
|
this.use(owner, method);
|
|
338
|
-
|
|
572
|
+
const object = this.expression(expr.callee.object), path = this.memberPath(expr.callee);
|
|
573
|
+
target = owner ? this.link(owner, `${owner.name}.${method.name}`, path ?? expr.callee.name) : code(path ?? expr.callee.name);
|
|
574
|
+
if (!path)
|
|
575
|
+
target += ' on ' + object;
|
|
339
576
|
}
|
|
340
577
|
else
|
|
341
578
|
target = this.expression(expr.callee);
|
|
@@ -346,12 +583,19 @@ class SpecWriter {
|
|
|
346
583
|
const inputs = expr.args.map((arg, index) => {
|
|
347
584
|
const slot = plan?.sourceIndices.indexOf(index);
|
|
348
585
|
const label = expr.argLabels[index] ?? (slot !== undefined && slot >= 0 ? parameters[slot]?.label ?? parameters[slot]?.name : arg.kind === 'name' ? arg.name : undefined);
|
|
349
|
-
|
|
586
|
+
const same = arg.kind === 'name' && arg.name === label || arg.kind === 'member' && arg.name === label;
|
|
587
|
+
const value = this.expression(arg);
|
|
588
|
+
return label && !same ? code(label) + (arg.kind === 'literal' ? ' ' : ' from ') + value : value;
|
|
589
|
+
});
|
|
590
|
+
let text = `${constructs ? 'construct' : 'call'} ${target}` + (expr.typeArgs.length ? ' for ' + coordinate(expr.typeArgs.map(type => this.type(type))) : '') + (inputs.length ? ' with ' + coordinate(inputs) : '');
|
|
591
|
+
const injected = parameters.flatMap((param, index) => {
|
|
592
|
+
if (!param.injected)
|
|
593
|
+
return [];
|
|
594
|
+
const source = plan?.injectionSources?.[index] ?? plan?.bindingKeys[index] ?? typeName(param.type);
|
|
595
|
+
return [code(source) + (source === param.name ? '' : ' for ' + code(param.name))];
|
|
350
596
|
});
|
|
351
|
-
let text = `call ${target}` + (expr.typeArgs.length ? ' with type arguments ' + expr.typeArgs.map(type => this.type(type)).join(', ') : '') + (inputs.length ? ' with ' + inputs.join('; ') : '');
|
|
352
|
-
const injected = parameters.flatMap((param, index) => param.injected ? [`${code(param.name)} from ${code(plan?.injectionSources?.[index] ?? plan?.bindingKeys[index] ?? typeName(param.type))}`] : []);
|
|
353
597
|
if (injected.length)
|
|
354
|
-
text += '
|
|
598
|
+
text += ' using injected ' + coordinate(injected);
|
|
355
599
|
return text;
|
|
356
600
|
}
|
|
357
601
|
stringParts(expr) {
|
|
@@ -359,21 +603,55 @@ class SpecWriter {
|
|
|
359
603
|
return [...this.stringParts(expr.left), ...this.stringParts(expr.right)];
|
|
360
604
|
return [expr];
|
|
361
605
|
}
|
|
362
|
-
joinedText(target, value
|
|
363
|
-
|
|
364
|
-
if (parts.length < 4)
|
|
606
|
+
joinedText(target, value) {
|
|
607
|
+
if (this.stringParts(value).length < 2)
|
|
365
608
|
return;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
609
|
+
return step(`It builds ${target} as ${this.expression(value)}.`);
|
|
610
|
+
}
|
|
611
|
+
headerFields(value) {
|
|
612
|
+
let current = value;
|
|
613
|
+
const calls = [];
|
|
614
|
+
while (current.kind === 'call' && current.callee.kind === 'member' && current.callee.name === 'with' &&
|
|
615
|
+
this.checked.expressionTypes.get(current.callee.object)?.name === 'Headers') {
|
|
616
|
+
const labels = current.args.map((arg, index) => current.kind === 'call' ?
|
|
617
|
+
current.argLabels[index] ?? (arg.kind === 'name' ? arg.name : '') : ''), name = labels.indexOf('name'), value = labels.indexOf('value');
|
|
618
|
+
if (name < 0 || value < 0)
|
|
619
|
+
return;
|
|
620
|
+
calls.unshift(current);
|
|
621
|
+
current = current.callee.object;
|
|
622
|
+
}
|
|
623
|
+
if (calls.length < 3)
|
|
624
|
+
return;
|
|
625
|
+
const operation = collectionOperations.Headers?.find(operation => operation.name === 'with');
|
|
626
|
+
if (operation)
|
|
627
|
+
this.builtins.set('Headers.with', operation);
|
|
628
|
+
return { base: current, fields: calls.map(call => {
|
|
629
|
+
const labels = call.args.map((arg, index) => call.argLabels[index] ?? (arg.kind === 'name' ? arg.name : ''));
|
|
630
|
+
return `${this.expression(call.args[labels.indexOf('name')])} to ${this.expression(call.args[labels.indexOf('value')])}`;
|
|
631
|
+
}) };
|
|
632
|
+
}
|
|
633
|
+
statements(body) {
|
|
634
|
+
const result = [];
|
|
635
|
+
for (let index = 0; index < body.length; index++) {
|
|
636
|
+
const stmt = body[index], nodes = this.statement(stmt), tail = body[index + 1];
|
|
637
|
+
// A terminal guard and final return are exact alternatives, even without a written else.
|
|
638
|
+
if (stmt.kind === 'if' && !stmt.otherwise.length && index === body.length - 2 && tail?.kind === 'return' &&
|
|
639
|
+
stmt.then.at(-1)?.kind === 'return' && nodes[0].kind === 'branch') {
|
|
640
|
+
nodes[0].otherwise = this.statement(tail);
|
|
641
|
+
index++;
|
|
642
|
+
}
|
|
643
|
+
result.push(...nodes);
|
|
644
|
+
}
|
|
645
|
+
return result;
|
|
369
646
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
647
|
+
statement(stmt) {
|
|
648
|
+
const nodes = this.explainStatement(stmt);
|
|
649
|
+
// Source identities survive sentence aggregation. Two identical calls are still two facts.
|
|
650
|
+
nodes[0].source = `${stmt.span.file}:${stmt.span.start}:${stmt.span.end}`;
|
|
651
|
+
return nodes;
|
|
374
652
|
}
|
|
375
|
-
|
|
376
|
-
const
|
|
653
|
+
explainStatement(stmt) {
|
|
654
|
+
const nested = (body) => this.statements(body);
|
|
377
655
|
switch (stmt.kind) {
|
|
378
656
|
case 'assign': {
|
|
379
657
|
if (stmt.target.kind === 'name') {
|
|
@@ -386,42 +664,133 @@ class SpecWriter {
|
|
|
386
664
|
this.locals.set(stmt.target.name, { name: def.name, args: [], nullable: false, span: stmt.span });
|
|
387
665
|
}
|
|
388
666
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
667
|
+
if (stmt.declaredType)
|
|
668
|
+
this.type(stmt.declaredType);
|
|
669
|
+
const target = this.expression(stmt.target) + (stmt.declaredType && !['int', 'float', 'string', 'bool'].includes(stmt.declaredType.name) ? ' of type ' + this.type(stmt.declaredType) : '');
|
|
670
|
+
const headers = this.headerFields(stmt.value);
|
|
671
|
+
const arithmetic = stmt.value.kind === 'binary' && ['+', '-'].includes(stmt.value.op) && stmt.value.left.kind === 'name' && stmt.target.kind === 'name' && stmt.target.name === stmt.value.left.name && ['int', 'float', 'c_int'].includes(this.checked.expressionTypes.get(stmt.value)?.name ?? '');
|
|
672
|
+
const headerLead = headers ? `set ${target} from ${this.expression(headers.base)} by adding these header fields in order` : undefined;
|
|
673
|
+
const getter = stmt.value.kind === 'call' && !stmt.value.args.length && stmt.value.callee.kind === 'name' && this.definition(stmt.value.callee.name)?.node.kind === 'function';
|
|
674
|
+
const explanation = headers ? sequence('It ' + headerLead.replace(/^set /, 'sets '), headers.fields, headerLead) :
|
|
675
|
+
getter ? step('It gets ' + target + ' from ' + this.expression(stmt.value) + '.') :
|
|
676
|
+
this.joinedText(target, stmt.value) ?? (arithmetic && stmt.value.kind === 'binary' ? action(stmt.value.op === '+' ? 'increase' : 'decrease', `${target} by ${this.expression(stmt.value.right)}`) : action('set', `${target} to ${this.expression(stmt.value)}`));
|
|
677
|
+
return stmt.ownership === 'own' ? [explanation, step(`${target} owns this value.`)] : [explanation];
|
|
678
|
+
}
|
|
679
|
+
case 'destructure': return [action('split', `${this.expression(stmt.value)} into ${coordinate(stmt.names.map(code))} in order`)];
|
|
680
|
+
case 'expr': {
|
|
681
|
+
if (stmt.expr.kind === 'literal' && stmt.expr.value === null)
|
|
682
|
+
return [action('continue', 'without an operation')];
|
|
683
|
+
if (stmt.expr.kind === 'call') {
|
|
684
|
+
const call = this.call(stmt.expr);
|
|
685
|
+
if (stmt.expr.callee.kind === 'name' && !this.definition(stmt.expr.callee.name)) {
|
|
686
|
+
if (stmt.expr.callee.name === 'print')
|
|
687
|
+
return [step('It prints ' + this.expression(stmt.expr.args[0]) + '.')];
|
|
688
|
+
if (stmt.expr.callee.name === 'assert')
|
|
689
|
+
return [step('The test requires ' + this.condition(stmt.expr.args[0]) + '.')];
|
|
690
|
+
}
|
|
691
|
+
if (stmt.expr.callee.kind === 'member') {
|
|
692
|
+
const receiver = stmt.expr.callee.object, def = this.receiver(receiver, stmt.expr.callee.name), method = def && this.method(def, stmt.expr.callee.name);
|
|
693
|
+
const type = this.checked.expressionTypes.get(receiver)?.name;
|
|
694
|
+
const arg = (name) => this.expression(this.argument(stmt.expr, name));
|
|
695
|
+
if (!def) {
|
|
696
|
+
if (type === 'List' && stmt.expr.callee.name === 'append')
|
|
697
|
+
return [step('It appends ' + arg('value') + ' to ' + this.expression(receiver) + '.')];
|
|
698
|
+
if (type === 'Set' && stmt.expr.callee.name === 'add')
|
|
699
|
+
return [step('It adds ' + arg('value') + ' to ' + this.expression(receiver) + '.')];
|
|
700
|
+
if (type === 'Map' && stmt.expr.callee.name === 'set')
|
|
701
|
+
return [step('It stores ' + arg('value') + ' in ' + this.expression(receiver) + ' under ' + arg('key') + '.')];
|
|
702
|
+
if (type === 'Map' && stmt.expr.callee.name === 'take')
|
|
703
|
+
return [step('It removes the key ' + arg('key') + ' from ' + this.expression(receiver) + '.')];
|
|
704
|
+
}
|
|
705
|
+
if (method && callableResult(this.checked, method).name === 'void' && method.params.filter(param => !param.injected).length === 1 && stmt.expr.args.length === 1) {
|
|
706
|
+
const owner = [...this.checked.project.definitions.values()].find(owner => 'methods' in owner.node && owner.node.methods.includes(method));
|
|
707
|
+
const target = this.link(owner, owner.name + '.' + method.name, this.memberPath(stmt.expr.callee) ?? method.name);
|
|
708
|
+
const plan = this.checked.callPlans.get(stmt.expr);
|
|
709
|
+
const injected = method.params.flatMap((param, index) => param.injected ? [code(plan?.injectionSources?.[index] ?? plan?.bindingKeys[index] ?? typeName(param.type))] : []);
|
|
710
|
+
return [step('It passes ' + this.expression(stmt.expr.args[0]) + ' to ' + target + (injected.length ? ', using injected ' + coordinate(injected) : '') + '.')];
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const stored = this.standardOperation(stmt.expr);
|
|
714
|
+
if (stored === 'memory/store.aug:ExpiringStore.put' || stored === 'memory/store.aug:MemoryStore.put') {
|
|
715
|
+
const arg = (name) => this.expression(this.argument(stmt.expr, name));
|
|
716
|
+
return [step(`It stores ${arg('value')} in ${this.expression(stmt.expr.callee.kind === 'member' ? stmt.expr.callee.object : stmt.expr.callee)} under ${arg('key')}, expiring at ${arg('expires')}. The current time for this write is ${arg('now')}.`)];
|
|
717
|
+
}
|
|
718
|
+
if (call.startsWith('call '))
|
|
719
|
+
return [action('call', call.slice(5))];
|
|
720
|
+
if (call.startsWith('construct '))
|
|
721
|
+
return [action('construct', call.slice(10))];
|
|
722
|
+
}
|
|
723
|
+
return [action('evaluate', this.expression(stmt.expr))];
|
|
392
724
|
}
|
|
393
|
-
case 'destructure': return lead + `Split ${this.expression(stmt.value)} into ${stmt.names.map(code).join(', ')}, in that order.\n`;
|
|
394
|
-
case 'expr': return lead + (stmt.expr.kind === 'literal' && stmt.expr.value === null ? 'Continue without another operation' : stmt.expr.kind === 'call' ? 'Call ' + this.call(stmt.expr).slice(5) : 'Evaluate ' + this.expression(stmt.expr)) + '.\n';
|
|
395
725
|
case 'return': {
|
|
396
726
|
if (!stmt.value)
|
|
397
|
-
return
|
|
398
|
-
const
|
|
399
|
-
|
|
727
|
+
return [action('return', 'without a value')];
|
|
728
|
+
const headers = this.headerFields(stmt.value);
|
|
729
|
+
if (headers) {
|
|
730
|
+
const lead = `return headers starting with ${this.expression(headers.base)} and adding these fields in order`;
|
|
731
|
+
return [sequence('It ' + lead.replace(/^return /, 'returns '), headers.fields, lead)];
|
|
732
|
+
}
|
|
733
|
+
return [action('return', this.expression(stmt.value))];
|
|
734
|
+
}
|
|
735
|
+
case 'throw': return [action('fail', 'with ' + this.expression(stmt.value))];
|
|
736
|
+
case 'yield': return [action('send', this.expression(stmt.value) + ' as the next stream item')];
|
|
737
|
+
case 'if': {
|
|
738
|
+
const node = branch(this.condition(stmt.test), nested(stmt.then), nested(stmt.otherwise));
|
|
739
|
+
if (node.kind === 'branch' && !stmt.otherwise.length && stmt.then.length === 1 && stmt.then[0].kind === 'throw' &&
|
|
740
|
+
stmt.then[0].value.kind === 'call' && stmt.then[0].value.args.length === 0)
|
|
741
|
+
node.requirement = this.requirement(stmt.test);
|
|
742
|
+
return [node];
|
|
743
|
+
}
|
|
744
|
+
case 'while': return [loop('While ' + this.condition(stmt.test), nested(stmt.body), 'Repeat this loop while its condition remains true.')];
|
|
745
|
+
case 'for': return [loop('For each ' + coordinate(stmt.names.map(code)) + ' in a snapshot of ' + this.expression(stmt.iterable), nested(stmt.body), 'Repeat these steps for each remaining item in the snapshot.')];
|
|
746
|
+
case 'match': {
|
|
747
|
+
const value = this.expression(stmt.value);
|
|
748
|
+
const terminal = (body) => ['return', 'throw'].includes(body.at(-1)?.kind ?? '');
|
|
749
|
+
// A null guard that exits is followed only by the non-null path. This is
|
|
750
|
+
// a sequential explanation, not a nested tour through match syntax.
|
|
751
|
+
if (stmt.cases.length === 2 && stmt.cases[0].pattern === 'null' && terminal(stmt.cases[0].body) && stmt.cases[1].pattern === 'some') {
|
|
752
|
+
const read = stmt.value.kind === 'call', second = stmt.cases[1];
|
|
753
|
+
const first = branch(read ? 'no value is found' : value + ' is null', nested(stmt.cases[0].body));
|
|
754
|
+
return [...(read ? [step('It obtains ' + value + '.')] : []), first,
|
|
755
|
+
step('The non-null ' + (read ? 'result' : value) + ' becomes ' + code(second.name) + '.'), ...nested(second.body)];
|
|
756
|
+
}
|
|
757
|
+
return [choice(value, stmt.cases.map(clause => {
|
|
758
|
+
const condition = clause.pattern === 'else' ? 'Otherwise' : clause.pattern === 'some' ? 'If ' + value + ' is not null, using ' + code(clause.name) + ' for it' :
|
|
759
|
+
clause.pattern === 'type' ? 'If ' + value + ' satisfies ' + this.type(clause.type) + ', using ' + code(clause.name) + ' for it' :
|
|
760
|
+
clause.pattern === 'literal' ? 'If ' + value + ' equals ' + this.expression(clause.literal) : 'If ' + value + ' is null';
|
|
761
|
+
return { condition, children: nested(clause.body) };
|
|
762
|
+
}))];
|
|
400
763
|
}
|
|
401
|
-
case '
|
|
402
|
-
case '
|
|
403
|
-
case '
|
|
404
|
-
case '
|
|
405
|
-
case '
|
|
406
|
-
case '
|
|
407
|
-
|
|
408
|
-
clause.pattern === 'type' ? 'A value that satisfies ' + this.type(clause.type) + ', named ' + code(clause.name) :
|
|
409
|
-
clause.pattern === 'literal' ? this.expression(clause.literal) : 'A null value, including omitted optional input';
|
|
410
|
-
return ' '.repeat(depth + 1) + '- ' + pattern + ':\n' + this.statements(clause.body, depth + 2);
|
|
411
|
-
}).join('');
|
|
412
|
-
case 'try': return lead + 'Try these operations:\n' + nested(stmt.body) + stmt.catches.map(clause => lead + 'If they fail with ' + this.type(clause.type) + ', name the failure ' + code(clause.name) + ' and recover:\n' + nested(clause.body)).join('') +
|
|
413
|
-
(stmt.always ? lead + 'On both success and failure, perform cleanup:\n' + nested(stmt.always) : '');
|
|
414
|
-
case 'unsafe': return lead + 'Enter an unsafe boundary. Native calls use their declared contracts; their foreign implementation is outside this specification:\n' + nested(stmt.body);
|
|
415
|
-
case 'borrow': return lead + 'Grant exclusive mutable access to ' + code(stmt.name) + ' for this block, then end the borrow:\n' + nested(stmt.body);
|
|
416
|
-
case 'scope': return lead + 'Create a dependency and task scope. Join its child tasks and release scoped values before leaving:\n' + nested(stmt.body);
|
|
417
|
-
case 'lock': return lead + 'Lock ' + this.expression(stmt.value) + ', expose its mutable value as ' + code(stmt.name) + ', and release the lock on every exit:\n' + nested(stmt.body);
|
|
418
|
-
case 'freeze': return lead + 'Freeze ' + this.expression(stmt.value) + ' as ' + code(stmt.name) + '. Share the immutable value without copying it.\n';
|
|
419
|
-
case 'serve': return lead + 'Serve ' + stmt.names.map(name => { const def = this.definition(name); return def ? this.link(def) : code(name); }).join(', ') + ' on port ' + this.expression(stmt.port) + '.\n';
|
|
764
|
+
case 'try': return [attempt(nested(stmt.body), stmt.catches.map(clause => ({ error: this.type(clause.type), name: code(clause.name), children: nested(clause.body) })), stmt.always ? nested(stmt.always) : undefined)];
|
|
765
|
+
case 'unsafe': return [scope('Within an unsafe block', nested(stmt.body), 'Native operations must satisfy their declared C contracts.')];
|
|
766
|
+
case 'borrow': return [scope('With temporary permission to change ' + code(stmt.name), nested(stmt.body), '')];
|
|
767
|
+
case 'scope': return [scope('Within a task and ownership scope', nested(stmt.body), 'On leaving this scope, join its child tasks and release its local values.')];
|
|
768
|
+
case 'lock': return [scope('While holding the lock on ' + this.expression(stmt.value) + ' as mutable ' + code(stmt.name), nested(stmt.body), 'Release this lock when the block exits, including on return or failure.')];
|
|
769
|
+
case 'freeze': return [action('freeze', this.expression(stmt.value) + ' as ' + code(stmt.name)), step('The immutable value is shared without copying it.')];
|
|
770
|
+
case 'serve': return [action('serve', coordinate(stmt.names.map(name => { const def = this.definition(name); return def ? this.link(def) : code(name); })) + ' on port ' + this.expression(stmt.port))];
|
|
420
771
|
default: return unreachable(stmt);
|
|
421
772
|
}
|
|
422
773
|
}
|
|
423
774
|
condition(expr) {
|
|
424
|
-
return this.expression(expr) + (['binary', 'unary'].includes(expr.kind) ? '' : ' is true');
|
|
775
|
+
return expr.kind === 'call' ? this.predicate(expr) : this.expression(expr) + (['binary', 'unary'].includes(expr.kind) ? '' : ' is true');
|
|
776
|
+
}
|
|
777
|
+
requirement(expr, nested = false) {
|
|
778
|
+
if (expr.kind === 'unary' && expr.op === '!')
|
|
779
|
+
return this.condition(expr.value);
|
|
780
|
+
if (expr.kind === 'binary') {
|
|
781
|
+
if (expr.op === '||' || expr.op === '&&') {
|
|
782
|
+
if (expr.op === '&&' && expr.left.kind === 'binary' && expr.right.kind === 'binary' && expr.left.op === '!=' && expr.right.op === '!=' &&
|
|
783
|
+
this.memberPath(expr.left.left) === this.memberPath(expr.right.left) && this.memberPath(expr.left.left))
|
|
784
|
+
return this.expression(expr.left.left) + ' is either ' + this.expression(expr.left.right) + ' or ' + this.expression(expr.right.right);
|
|
785
|
+
const child = (value) => this.requirement(value, value.kind === 'binary' && ['&&', '||'].includes(value.op) && value.op !== expr.op);
|
|
786
|
+
const text = child(expr.left) + (expr.op === '||' ? ' and ' : ' or ') + child(expr.right);
|
|
787
|
+
return nested ? '(' + text + ')' : text;
|
|
788
|
+
}
|
|
789
|
+
const inverse = { '==': 'does not equal', '!=': 'equals', '<': 'is at least', '>': 'is at most', '<=': 'is greater than', '>=': 'is less than' };
|
|
790
|
+
if (inverse[expr.op])
|
|
791
|
+
return this.expression(expr.left, true) + ' ' + inverse[expr.op] + ' ' + this.expression(expr.right, true);
|
|
792
|
+
}
|
|
793
|
+
return expr.kind === 'call' ? this.predicate(expr, false) : this.expression(expr) + ' is false';
|
|
425
794
|
}
|
|
426
795
|
binding(binding) {
|
|
427
796
|
const info = this.checked.bindings.find(info => info.declaration === binding);
|
|
@@ -429,256 +798,232 @@ class SpecWriter {
|
|
|
429
798
|
this.use(def);
|
|
430
799
|
const lifetime = binding.lifetime ?? info?.lifetime;
|
|
431
800
|
const key = binding.key + (binding.keyTypeArgs.length ? '<' + binding.keyTypeArgs.map(type => typeName(type)).join(', ') + '>' : '');
|
|
432
|
-
return (
|
|
433
|
-
(lifetime === 'shared' ? '
|
|
434
|
-
(binding.sharedMutation ? '
|
|
435
|
-
(info?.dependencies.length ? '
|
|
801
|
+
return (`${code(key)} is provided by ${def ? this.link(def, def.name, typeName(binding.target)) : this.type(binding.target)}. ` +
|
|
802
|
+
(lifetime === 'shared' ? 'The same instance is shared. ' : lifetime === 'scoped' ? 'Each scope shares one instance. ' : lifetime === 'fresh' ? 'Each resolve creates a new instance. ' : 'Stateless instances are reused; stateful instances are created for each resolve. ') +
|
|
803
|
+
(binding.sharedMutation ? 'Shared mutation is allowed. ' : '') +
|
|
804
|
+
(info?.dependencies.length ? 'It requires bindings for ' + coordinate(info.dependencies.map(code)) + '. ' : '')).trimEnd();
|
|
436
805
|
}
|
|
437
806
|
callable(method, owner) {
|
|
438
807
|
this.locals = new Map(owner && 'fields' in owner.node ? fieldsOf(owner.node).map(field => [field.name, field.type]) : []);
|
|
439
808
|
const name = owner && owner.node.kind !== 'function' ? `${owner.name}.${method.name}` : method.name;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
809
|
+
const documentation = callableDocumentation(this.checked.project, method, owner);
|
|
810
|
+
const children = [];
|
|
811
|
+
if (method.endpoint)
|
|
812
|
+
children.push(paragraph(code(method.name) + ' handles ' + code(method.endpoint.method + ' ' + method.endpoint.path) + '.'));
|
|
813
|
+
if (method.name.startsWith('_'))
|
|
814
|
+
children.push(paragraph('It is private to its defining scope.'));
|
|
815
|
+
const notes = this.notes(documentation);
|
|
816
|
+
if (notes)
|
|
817
|
+
children.push(paragraph(notes));
|
|
818
|
+
children.push(...this.contract(method, documentation), ...this.layers(method));
|
|
819
|
+
if (method.body)
|
|
820
|
+
children.push(flow(this.statements(method.body)));
|
|
821
|
+
else if (method.externC) {
|
|
822
|
+
const native = nativeFact(this.checked, method);
|
|
823
|
+
children.push(paragraph(native ? this.nativeDescription(native) : 'Native C implementation; only its declared contract is visible here.'));
|
|
824
|
+
}
|
|
825
|
+
return this.heading(name, method.span, owner && owner.node.kind !== 'function' ? 3 : 2, children);
|
|
826
|
+
}
|
|
827
|
+
exportLine(item) {
|
|
828
|
+
const folder = this.file.builtin ? libraryChild(this.checked.project.libraries, dirname(this.file.path), item.name) : join(dirname(this.file.path), item.name);
|
|
829
|
+
const file = item.folder ? join(folder, 'export.aug') : join(dirname(this.file.path), item.from + '.aug');
|
|
830
|
+
this.enqueue(file);
|
|
831
|
+
return `Export ${item.folder ? 'the folder ' : 'the declaration '}${code(item.name)} from [${code(basename(file))}](${url(relative(dirname(this.docs.get(this.file.path)), this.docs.get(file)))}${item.folder ? '' : '#' + encodeURIComponent(anchor(item.name))}).`;
|
|
832
|
+
}
|
|
833
|
+
providerLine(item) {
|
|
834
|
+
if (item.kind === 'bind')
|
|
835
|
+
return this.binding(item);
|
|
836
|
+
const def = this.definition(item.name);
|
|
837
|
+
return 'Include providers from ' + (def ? this.link(def) : code(item.name)) + '.';
|
|
444
838
|
}
|
|
445
839
|
declaration(item) {
|
|
446
840
|
switch (item.kind) {
|
|
447
|
-
case '
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const file = item.folder ? join(folder, 'export.aug') : join(dirname(this.file.path), item.from + '.aug');
|
|
451
|
-
this.enqueue(file);
|
|
452
|
-
return `- Export ${item.folder ? 'the folder ' : 'the declaration '}${code(item.name)} from [${code(basename(file))}](${url(relative(dirname(this.docs.get(this.file.path)), this.docs.get(file)))}${item.folder ? '' : '#' + encodeURIComponent(anchor(item.name))}).\n`;
|
|
841
|
+
case 'resource': {
|
|
842
|
+
const native = nativeFact(this.checked, item);
|
|
843
|
+
return this.heading(item.name, item.span, 2, [paragraph(native ? this.nativeDescription(native) : code(item.name) + ' is an opaque native resource with an unresolved package release contract.')], 'native resource');
|
|
453
844
|
}
|
|
454
845
|
case 'function': return this.callable(item);
|
|
455
846
|
case 'class': {
|
|
456
847
|
const def = this.definition(item.name);
|
|
457
848
|
this.locals = new Map();
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
849
|
+
const documentation = javadocBefore(this.file.source, item.annotations?.[0]?.span.start ?? item.span.start);
|
|
850
|
+
const children = [];
|
|
851
|
+
const generics = this.generics(item), notes = this.notes(documentation);
|
|
852
|
+
const inputs = this.inputs(item.fields, true, this.file.path, documentation?.parameters);
|
|
853
|
+
const implementsText = item.implements.length ? 'It implements ' + coordinate(item.implements.map(type => this.type(type))) + '.' : '';
|
|
854
|
+
const introduction = [notes, implementsText, item.name.startsWith('_') ? 'It is private to this file.' : '', generics].filter(Boolean).join(' ');
|
|
855
|
+
if (introduction)
|
|
856
|
+
children.push(paragraph(introduction));
|
|
857
|
+
if (inputs)
|
|
858
|
+
children.push(paragraph(inputs));
|
|
859
|
+
children.push(...this.layers(item));
|
|
860
|
+
const errors = this.checked.constructorContracts.get(item)?.errors;
|
|
861
|
+
if (errors?.length)
|
|
862
|
+
children.push(paragraph('Construction can fail with ' + errors.map(type => { this.use(type.def); return type.def ? this.link(type.def) : code(tyName(type)); }).join(', ') + '.'));
|
|
863
|
+
else if (item.validationErrors?.length)
|
|
864
|
+
children.push(paragraph('Construction can fail with ' + item.validationErrors.map(type => this.type(type)).join(', ') + '.'));
|
|
463
865
|
if (item.stateFields?.length)
|
|
464
|
-
|
|
866
|
+
children.push(...item.stateFields.map(field => { this.locals.set(field.name, field.type); return paragraph(`The ${field.mutable ? 'mutable' : 'read-only'}${field.name.startsWith('_') ? ', private' : ''} field ${code(field.name)} has type ${this.type(field.type)} and starts as ${this.expression(field.initializer)}.`); }));
|
|
465
867
|
if (item.constructorBody)
|
|
466
|
-
|
|
868
|
+
children.push(this.heading(item.name + '.initialize', item.span, 3, [flow(this.statements(item.constructorBody))]));
|
|
467
869
|
if (!item.record) {
|
|
468
870
|
const defaults = this.checked.defaults.get(def.id);
|
|
469
871
|
const inherited = [...(defaults?.values() ?? [])].filter(info => !item.methods.some(method => method.name === info.method.name));
|
|
470
872
|
if (inherited.length)
|
|
471
|
-
|
|
873
|
+
children.push(paragraph('It inherits the default implementations of ' + coordinate(inherited.map(info => { const parent = this.checked.project.definitions.get(info.from); this.use(parent, info.method); return parent ? this.link(parent, `${parent.name}.${info.method.name}`) : code(info.method.name); })) + '.'));
|
|
472
874
|
}
|
|
473
|
-
|
|
875
|
+
children.push(...item.methods.map(method => this.callable(method, def)));
|
|
876
|
+
return this.heading(item.name, item.span, 2, children, item.record ? 'immutable record' : 'class');
|
|
474
877
|
}
|
|
475
878
|
case 'interface':
|
|
476
879
|
case 'interceptor': {
|
|
477
880
|
const def = this.definition(item.name);
|
|
478
881
|
this.locals = new Map();
|
|
479
|
-
|
|
480
|
-
|
|
882
|
+
const generics = this.generics(item), notes = this.notes(javadocBefore(this.file.source, item.span.start));
|
|
883
|
+
const role = item.kind === 'interceptor' ? 'interceptor' : item.capability ? 'capability interface' : 'interface';
|
|
884
|
+
const introduction = [notes, item.name.startsWith('_') ? 'It is private to this file.' : '', generics].filter(Boolean).join(' ');
|
|
885
|
+
const children = introduction ? [paragraph(introduction)] : [];
|
|
481
886
|
if (item.kind === 'interface' && item.extends.length)
|
|
482
|
-
|
|
483
|
-
if (item.kind === 'interceptor')
|
|
484
|
-
|
|
485
|
-
|
|
887
|
+
children.push(paragraph('It inherits ' + coordinate(item.extends.map(type => this.type(type))) + '.'));
|
|
888
|
+
if (item.kind === 'interceptor') {
|
|
889
|
+
const inputs = this.inputs(item.fields, true);
|
|
890
|
+
if (inputs)
|
|
891
|
+
children.push(paragraph(inputs));
|
|
892
|
+
children.push(paragraph('Creates one interceptor per invocation. Its around operation may delegate once or finish early.'));
|
|
893
|
+
}
|
|
894
|
+
children.push(...item.methods.map(method => this.callable(method, def)));
|
|
895
|
+
return this.heading(item.name, item.span, 2, children, role);
|
|
486
896
|
}
|
|
487
|
-
case 'composition':
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const def = this.definition(item.name);
|
|
491
|
-
return '- Include the providers from ' + (def ? this.link(def) : code(item.name)) + ' before execution.\n';
|
|
897
|
+
case 'composition': {
|
|
898
|
+
const notes = this.notes(javadocBefore(this.file.source, item.span.start));
|
|
899
|
+
return this.heading(item.name, item.span, 2, [...(notes ? [paragraph(notes)] : []), paragraph('These providers are registered before startup. ' + item.bindings.map(binding => this.binding(binding)).join(' '))]);
|
|
492
900
|
}
|
|
493
901
|
case 'test': return this.tests(item);
|
|
494
|
-
|
|
902
|
+
case 'bind':
|
|
903
|
+
case 'include': return paragraph(this.providerLine(item));
|
|
904
|
+
case 'export': return paragraph(this.exportLine(item));
|
|
905
|
+
case 'import': return paragraph('');
|
|
906
|
+
default: return flow(this.statement(item));
|
|
495
907
|
}
|
|
496
908
|
}
|
|
497
909
|
tests(suite) {
|
|
498
910
|
this.locals = new Map();
|
|
499
|
-
|
|
911
|
+
const children = [paragraph('Tests ' + this.type(suite.type) + '. Each case gets fresh setup and dependencies.')];
|
|
500
912
|
for (const group of suite.groups) {
|
|
501
913
|
this.locals = new Map();
|
|
502
|
-
|
|
914
|
+
const groupChildren = [];
|
|
503
915
|
if (group.setup.length)
|
|
504
|
-
|
|
916
|
+
groupChildren.push(paragraph('Setup for each case:'), flow(group.setup.flatMap(entry => entry.kind === 'bind' || entry.kind === 'include' ? [step(this.providerLine(entry))] : this.statement(entry))));
|
|
505
917
|
const setupLocals = new Map(this.locals);
|
|
506
918
|
for (const test of group.cases) {
|
|
507
919
|
this.locals = new Map(setupLocals);
|
|
508
|
-
|
|
920
|
+
const caseChildren = [];
|
|
509
921
|
if (test.parameters)
|
|
510
|
-
|
|
511
|
-
|
|
922
|
+
caseChildren.push(paragraph('Run once for each row of ' + test.rows.map(row => this.expression(row)).join('; ') + '. Bind row positions to ' + test.parameters.map(code).join(', ') + '.'));
|
|
923
|
+
caseChildren.push(flow(this.statements(test.body)));
|
|
924
|
+
groupChildren.push(section(code(test.name), 4, caseChildren, undefined, this.source(test.span)));
|
|
512
925
|
}
|
|
926
|
+
children.push(section(code(group.name), 3, groupChildren));
|
|
513
927
|
}
|
|
514
|
-
return
|
|
928
|
+
return this.heading(this.testTitle(suite), suite.span, 2, children);
|
|
515
929
|
}
|
|
516
930
|
testTitle(suite) {
|
|
517
931
|
return 'test ' + suite.type.name + (suite.name === suite.type.name ? '' : ' ' + suite.name);
|
|
518
932
|
}
|
|
519
933
|
dependencySurface() {
|
|
520
934
|
if (!this.used.size)
|
|
521
|
-
return
|
|
935
|
+
return;
|
|
522
936
|
const entries = [...this.used.values()].sort((a, b) => compare(relative(this.checked.project.root, this.docs.get(a.def.file)) + ':' + a.def.name, relative(this.checked.project.root, this.docs.get(b.def.file)) + ':' + b.def.name));
|
|
523
|
-
|
|
937
|
+
const groups = new Map();
|
|
524
938
|
for (const entry of entries) {
|
|
525
939
|
const def = entry.def, node = def.node;
|
|
526
|
-
const kind = node.kind === 'class' && node.record ? 'record' : node.kind === 'interface' && node.capability ? 'capability interface' : node.kind;
|
|
527
|
-
text += '### ' + this.link(def) + '\n\n' + kind[0].toUpperCase() + kind.slice(1);
|
|
528
940
|
const imported = this.file.items.find(item => item.kind === 'import' && (this.checked.project.imports.get(item) ?? []).some(imported => imported.id === def.id));
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
for (const name of [...entry.fields].sort(compare)) {
|
|
539
|
-
const field = fieldsOf(node).find(field => field.name === name);
|
|
540
|
-
if (field)
|
|
541
|
-
text += '- Read ' + code(name) + ' (' + this.type(field.type, def.file) + ')' + (field.mutable ? '; its owner can change it' : '') + '.\n';
|
|
542
|
-
}
|
|
543
|
-
if ('methods' in node)
|
|
544
|
-
for (const method of [...entry.operations].sort((a, b) => compare(a.name, b.name)))
|
|
545
|
-
text += this.dependencyOperation(method, def);
|
|
546
|
-
if (node.kind !== 'function' && !entry.constructed && !entry.fields.size && !entry.operations.size)
|
|
547
|
-
text += 'Used as a type or provider.\n';
|
|
548
|
-
text += '\n';
|
|
549
|
-
}
|
|
550
|
-
return text;
|
|
551
|
-
}
|
|
552
|
-
shortInputs(params, file) {
|
|
553
|
-
return params.filter(param => !param.injected).map(param => code(param.label ?? param.name) + ': ' + this.type(param.type, file) +
|
|
554
|
-
(param.source ? ' from HTTP ' + param.source.kind + (param.source.name ? ' ' + code(param.source.name) : '') : '')).join(', ') || 'no caller inputs';
|
|
555
|
-
}
|
|
556
|
-
dependencyOperation(method, owner) {
|
|
557
|
-
const effects = this.checked.effectContracts.get(method), layers = this.checked.interceptorPlans.get(method) ?? [];
|
|
558
|
-
const changes = effects?.changes ?? method.changes ?? [];
|
|
559
|
-
const uses = [...(effects?.uses.values() ?? method.uses ?? [])];
|
|
560
|
-
const errors = [...new Set([...method.throws.map(type => typeName(type)), ...layers.flatMap(layer => layer.errors.map(tyName))])];
|
|
561
|
-
const name = owner.node.kind === 'function' ? owner.name : owner.name + '.' + method.name;
|
|
562
|
-
let text = '- ' + this.link(owner, name) + (method.typeParams.length ? '<' + method.typeParams.map(code).join(', ') + '>' : '') +
|
|
563
|
-
' (' + this.shortInputs(method.params, method.span.file) + ') → ' + this.type(method.returns, method.span.file);
|
|
564
|
-
const injected = method.params.filter(param => param.injected);
|
|
565
|
-
if (injected.length)
|
|
566
|
-
text += '; inject ' + injected.map(param => code(param.name) + ': ' + this.type(param.type, method.span.file)).join(', ');
|
|
567
|
-
if (changes.length)
|
|
568
|
-
text += '; changes ' + changes.map(code).join(', ');
|
|
569
|
-
const otherUses = uses.filter(effect => effect.source !== owner.name || effect.operation !== method.name);
|
|
570
|
-
if (otherUses.length)
|
|
571
|
-
text += '; uses ' + otherUses.map(effect => {
|
|
572
|
-
const target = ('capability' in effect ? effect.capability?.def : undefined) ?? this.checked.project.scopes.get(method.span.file)?.get(effect.source);
|
|
573
|
-
const operation = target && 'methods' in target.node ? target.node.methods.find(method => method.name === effect.operation) : undefined;
|
|
574
|
-
this.use(target, operation);
|
|
575
|
-
return target ? this.link(target, operation ? `${target.name}.${operation.name}` : target.name, `${effect.source}.${effect.operation}`) : code(`${effect.source}.${effect.operation}`);
|
|
576
|
-
}).join(', ');
|
|
577
|
-
if (errors.length)
|
|
578
|
-
text += '; can fail with ' + errors.map(code).join(', ');
|
|
579
|
-
return text + '.\n';
|
|
580
|
-
}
|
|
581
|
-
fileOverview() {
|
|
582
|
-
const lines = [];
|
|
583
|
-
const declaration = (name) => `[${code(name)}](${url(basename(this.docs.get(this.file.path)))}#${encodeURIComponent(anchor(name))})`;
|
|
584
|
-
for (const item of this.file.items) {
|
|
585
|
-
switch (item.kind) {
|
|
586
|
-
case 'class':
|
|
587
|
-
lines.push(`- ${declaration(item.name)} is ${item.record ? 'an immutable record' : 'a class'}${item.implements.length ? ' implementing ' + item.implements.map(type => code(typeName(type))).join(', ') : ''}.`);
|
|
588
|
-
break;
|
|
589
|
-
case 'interface':
|
|
590
|
-
lines.push(`- ${declaration(item.name)} is ${item.capability ? 'a capability interface' : 'an interface'}.`);
|
|
591
|
-
break;
|
|
592
|
-
case 'interceptor':
|
|
593
|
-
lines.push(`- ${declaration(item.name)} is an interceptor.`);
|
|
594
|
-
break;
|
|
595
|
-
case 'function':
|
|
596
|
-
lines.push(`- ${declaration(item.name)} ${item.endpoint ? `handles ${code(item.endpoint.method)} ${code(item.endpoint.path)}` : 'is a function'}${item.returns.name === 'void' ? '' : ` returning ${code(typeName(item.returns))}`}.`);
|
|
597
|
-
break;
|
|
598
|
-
case 'composition':
|
|
599
|
-
lines.push(`- ${declaration(item.name)} declares ${item.bindings.length} ${item.bindings.length === 1 ? 'provider' : 'providers'}.`);
|
|
600
|
-
break;
|
|
601
|
-
case 'bind': break;
|
|
602
|
-
case 'include':
|
|
603
|
-
lines.push(`- Include providers from ${code(item.name)}.`);
|
|
604
|
-
break;
|
|
605
|
-
case 'export':
|
|
606
|
-
lines.push(`- Export ${code(item.name)} from this folder.`);
|
|
607
|
-
break;
|
|
608
|
-
case 'test':
|
|
609
|
-
lines.push(`- ${declaration(this.testTitle(item))} is a same-file test suite.`);
|
|
610
|
-
break;
|
|
611
|
-
default: break;
|
|
612
|
-
}
|
|
941
|
+
const origin = imported?.kind === 'import' ? code(imported.from.join('.')) + (imported.everything ? ' through import everything' : '') : '';
|
|
942
|
+
const names = [this.link(def)];
|
|
943
|
+
const members = [...entry.operations].sort((a, b) => compare(a.name, b.name)).map(method => this.link(def, def.name + '.' + method.name, method.name));
|
|
944
|
+
members.push(...[...entry.fields].sort(compare).map(name => code(name)));
|
|
945
|
+
if (members.length)
|
|
946
|
+
names[0] += ' (' + coordinate(members) + ')';
|
|
947
|
+
const group = groups.get(origin) ?? [];
|
|
948
|
+
group.push(...names);
|
|
949
|
+
groups.set(origin, group);
|
|
613
950
|
}
|
|
614
|
-
|
|
615
|
-
if (providers.length)
|
|
616
|
-
lines.push(`- Register ${providers.length} ${providers.length === 1 ? 'dependency provider' : 'dependency providers'} before startup.`);
|
|
617
|
-
const executable = this.file.items.filter(item => !['import', 'export', 'test', 'bind', 'include', 'class', 'interface', 'interceptor', 'function', 'composition'].includes(item.kind));
|
|
618
|
-
if (executable.some(item => item.kind === 'try'))
|
|
619
|
-
lines.push('- Run startup operations with checked error recovery.');
|
|
620
|
-
for (const item of executable)
|
|
621
|
-
if (item.kind === 'serve')
|
|
622
|
-
lines.push(`- Serve ${item.names.length} HTTP ${item.names.length === 1 ? 'route' : 'routes'}.`);
|
|
623
|
-
const otherSteps = executable.filter(item => item.kind !== 'try' && item.kind !== 'serve').length;
|
|
624
|
-
if (otherSteps)
|
|
625
|
-
lines.push(`- Run ${otherSteps} other startup ${otherSteps === 1 ? 'step' : 'steps'} in source order.`);
|
|
626
|
-
return lines.length ? '## In this file\n\n' + lines.join('\n') + '\n\n' : '';
|
|
951
|
+
return section('Dependencies', 2, [...[...groups].map(([origin, names]) => paragraph('It uses ' + coordinate(names) + (origin ? ' from ' + origin : '') + '.'))]);
|
|
627
952
|
}
|
|
628
953
|
render() {
|
|
629
954
|
const exports = this.file.items.filter(item => item.kind === 'export');
|
|
630
|
-
const declarations = this.file.items.filter(item => ['class', 'interface', 'interceptor', 'function', 'composition'].includes(item.kind));
|
|
955
|
+
const declarations = this.file.items.filter(item => ['class', 'interface', 'interceptor', 'function', 'composition', 'resource'].includes(item.kind));
|
|
631
956
|
const providers = this.file.items.filter(item => item.kind === 'bind' || item.kind === 'include');
|
|
632
957
|
const startup = this.file.items.filter(item => item.kind !== 'import' && item.kind !== 'export' && item.kind !== 'test' && item.kind !== 'bind' && item.kind !== 'include' && !declarations.includes(item));
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
(
|
|
636
|
-
(startup.length ? '## Startup, in source order\n\n' + startup.map(item => this.declaration(item)).join('') + '\n' : '') +
|
|
637
|
-
declarations.sort((a, b) => Number('name' in a && a.name.startsWith('_')) - Number('name' in b && b.name.startsWith('_'))).map(item => this.declaration(item)).join('') +
|
|
638
|
-
this.file.items.filter(item => item.kind === 'test').map(item => this.declaration(item)).join('');
|
|
639
|
-
const surface = this.dependencySurface() + this.builtinSurface();
|
|
640
|
-
let config = '';
|
|
958
|
+
const children = [];
|
|
959
|
+
if (declarations.some(item => item.kind === 'function' && item.endpoint))
|
|
960
|
+
children.push(paragraph('Plain handler results default to HTTP 200 unless another status is declared. HttpResponse values choose their own status. Unhandled request failures return HTTP 500 and cancel the request tasks.'));
|
|
641
961
|
if (basename(this.file.path) === 'main.aug') {
|
|
642
962
|
const project = this.checked.project;
|
|
643
963
|
if (project.files.get(this.file.path)?.items.some(item => item.kind === 'serve'))
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
(
|
|
654
|
-
|
|
964
|
+
children.push(section('HTTP configuration', 2, [paragraph(`Listen on ${code(project.config.web.host)}. Limit request bodies to ${project.config.web.body_limit} bytes and buffered responses to ${project.config.web.response_limit} bytes. ` +
|
|
965
|
+
(project.config.web.tls.certificate ? 'Use TLS. ' : '') +
|
|
966
|
+
(project.config.web.http3 ? 'Enable HTTP/3. ' : '') +
|
|
967
|
+
(project.config.openapi.enabled ? `Serve OpenAPI at ${code(project.config.openapi.path)} and API docs at ${code(project.config.openapi.docs)}. ` : ''))]));
|
|
968
|
+
}
|
|
969
|
+
// Build the checked explanation first; rendering decides all spacing.
|
|
970
|
+
if (exports.length)
|
|
971
|
+
children.push(section('Exports', 2, exports.map(item => paragraph(this.exportLine(item)))));
|
|
972
|
+
if (providers.length)
|
|
973
|
+
children.push(section('Providers', 2, providers.map(item => paragraph(this.providerLine(item)))));
|
|
974
|
+
if (startup.length)
|
|
975
|
+
children.push(section('Startup', 2, [flow(startup.flatMap(item => this.statement(item)))]));
|
|
976
|
+
children.push(...declarations.sort((a, b) => Number('name' in a && a.name.startsWith('_')) - Number('name' in b && b.name.startsWith('_'))).map(item => this.declaration(item)));
|
|
977
|
+
children.push(...this.file.items.filter(item => item.kind === 'test').map(item => this.declaration(item)));
|
|
978
|
+
if (!children.length)
|
|
979
|
+
children.push(paragraph('This file declares no operations.'));
|
|
980
|
+
// Links may add inherited operation owners. Walk until the surface is closed.
|
|
981
|
+
const surfaceSize = () => [...this.used.values()].reduce((size, entry) => size + 1 + entry.operations.size + entry.fields.size + Number(entry.constructed ?? false), 0);
|
|
982
|
+
let surface, size = -1;
|
|
983
|
+
do {
|
|
984
|
+
size = surfaceSize();
|
|
985
|
+
surface = this.dependencySurface();
|
|
986
|
+
} while (surfaceSize() !== size);
|
|
987
|
+
if (surface)
|
|
988
|
+
children.push(surface);
|
|
989
|
+
const builtins = this.builtinSurface();
|
|
990
|
+
if (builtins)
|
|
991
|
+
children.push(builtins);
|
|
992
|
+
return generated + '\n\n' + renderSpecTree(section(code(basename(this.file.path)), 1, children));
|
|
655
993
|
}
|
|
656
994
|
builtinSurface() {
|
|
657
995
|
if (!this.builtins.size && !this.properties.size)
|
|
658
|
-
return
|
|
659
|
-
return '
|
|
660
|
-
[...this.builtins].sort(([a], [b]) => compare(a, b)).map(([name, operation]) => `- ${code(name)} (${operation.parameters.map(param => code(param.label) + ': ' + code(param.type)).join(', ') || 'no inputs'}) → ${code(operation.returns)}: ${operation.documentation}` +
|
|
661
|
-
(operation.changes ? ' Changes the receiver.' : '') +
|
|
662
|
-
(operation.errors?.length ? ' Can fail with ' + operation.errors.map(code).join(', ') + '.' : '') + '\n').join('') +
|
|
663
|
-
'\n[Full built-in reference](https://greenpandastudios.github.io/augscript/language-constructs).\n\n';
|
|
996
|
+
return;
|
|
997
|
+
return paragraph('Built-in operations follow the [language reference](https://greenpandastudios.github.io/augscript/language-constructs).');
|
|
664
998
|
}
|
|
665
999
|
}
|
|
666
|
-
/**
|
|
1000
|
+
/** Refresh pointers and artifacts atomically, or check drift without writing; protect handwritten documents. */
|
|
667
1001
|
export function updateSpecs(checked, check = false, options = {}) {
|
|
668
1002
|
const outputs = generateSpecs(checked, options), root = checked.project.root;
|
|
669
1003
|
const manifest = join(root, '.aug-spec', 'manifest.json');
|
|
670
1004
|
let previous = [];
|
|
1005
|
+
const previousDescriptors = new Map();
|
|
1006
|
+
const descriptorPath = (path) => /^\.aug-spec\/packages\/(?:@[^/]+\/)?[^/]+\/[^/]+\/native\.abi\.json$/.test(path) && !path.includes('\\') && !path.split('/').some(part => part === '.' || part === '..');
|
|
671
1007
|
if (options.manifest !== false && existsSync(manifest)) {
|
|
672
1008
|
const saved = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
673
|
-
if (saved.format !== 1 || !Array.isArray(saved.files) || saved.files.some(path => typeof path !== 'string' || relative(root, resolve(root, path)).startsWith('..') || !path.endsWith('.aug') && !path.endsWith('.aug.md')))
|
|
1009
|
+
if (saved.format !== 1 || !Array.isArray(saved.files) || saved.files.some(path => typeof path !== 'string' || relative(root, resolve(root, path)).startsWith('..') || !path.endsWith('.aug') && !path.endsWith('.aug.md') && !descriptorPath(path)))
|
|
674
1010
|
throw new Error('Invalid generated specification manifest');
|
|
675
1011
|
previous = saved.files.map(path => resolve(root, path));
|
|
1012
|
+
for (const [path, digest] of Object.entries(saved.nativeDescriptors ?? {})) {
|
|
1013
|
+
if (!descriptorPath(path) || !saved.files.includes(path) || typeof digest !== 'string' || !/^[0-9a-f]{64}$/.test(digest))
|
|
1014
|
+
throw new Error('Invalid generated native descriptor manifest');
|
|
1015
|
+
previousDescriptors.set(resolve(root, path), digest);
|
|
1016
|
+
}
|
|
676
1017
|
}
|
|
677
|
-
const
|
|
1018
|
+
const artifacts = outputs.filter(output => output.kind !== 'source-hint');
|
|
1019
|
+
const names = new Set(artifacts.map(output => output.path));
|
|
678
1020
|
const removed = previous.filter(path => !names.has(path) && existsSync(path));
|
|
679
1021
|
const stale = outputs.filter(output => !existsSync(output.path) || readFileSync(output.path, 'utf8') !== output.text).map(output => relative(root, output.path));
|
|
680
1022
|
stale.push(...removed.map(path => relative(root, path)));
|
|
681
|
-
const
|
|
1023
|
+
const descriptors = artifacts.filter(output => output.kind === 'native-descriptor');
|
|
1024
|
+
const manifestText = JSON.stringify({ format: 1, files: artifacts.map(output => relative(root, output.path).replaceAll('\\', '/')),
|
|
1025
|
+
...(descriptors.length ? { nativeDescriptors: Object.fromEntries(descriptors.map(output => [relative(root, output.path).replaceAll('\\', '/'), hash(output.text)])) } : {}),
|
|
1026
|
+
digest: hash(artifacts.map(output => output.text).join('\0')) }, null, 2) + '\n';
|
|
682
1027
|
if (options.manifest !== false && (!existsSync(manifest) || readFileSync(manifest, 'utf8') !== manifestText))
|
|
683
1028
|
stale.push('.aug-spec/manifest.json');
|
|
684
1029
|
if (check)
|
|
@@ -697,39 +1042,58 @@ export function updateSpecs(checked, check = false, options = {}) {
|
|
|
697
1042
|
if (lstatSync(output.path).isSymbolicLink())
|
|
698
1043
|
throw new Error(`Specification output is a symbolic link: ${output.path}`);
|
|
699
1044
|
const text = readFileSync(output.path, 'utf8');
|
|
700
|
-
if (
|
|
1045
|
+
if (output.kind === 'source-hint') {
|
|
1046
|
+
if (text !== checked.project.files.get(output.source)?.source)
|
|
1047
|
+
throw new Error('Source changed during compilation; retry before generating specifications');
|
|
1048
|
+
}
|
|
1049
|
+
else if (output.kind === 'native-descriptor') {
|
|
1050
|
+
// An unchanged descriptor from an earlier preview may be adopted. A
|
|
1051
|
+
// tracked copy must still match its recorded bytes before replacement.
|
|
1052
|
+
const tracked = previousDescriptors.get(output.path);
|
|
1053
|
+
if (tracked ? hash(text) !== tracked : text !== output.text)
|
|
1054
|
+
throw new Error(`Refusing to overwrite edited native descriptor ${output.path}`);
|
|
1055
|
+
}
|
|
1056
|
+
else if (!text.startsWith(generated) && !text.startsWith(copied))
|
|
701
1057
|
throw new Error(`Refusing to overwrite handwritten file ${output.path}`);
|
|
702
1058
|
}
|
|
703
1059
|
}
|
|
1060
|
+
for (const path of removed)
|
|
1061
|
+
if (previousDescriptors.has(path) &&
|
|
1062
|
+
(lstatSync(path).isSymbolicLink() || hash(readFileSync(path, 'utf8')) !== previousDescriptors.get(path)))
|
|
1063
|
+
throw new Error(`Refusing to remove edited native descriptor ${path}`);
|
|
704
1064
|
for (const output of outputs) {
|
|
705
1065
|
if (existsSync(output.path) && readFileSync(output.path, 'utf8') === output.text)
|
|
706
1066
|
continue;
|
|
707
1067
|
mkdirSync(dirname(output.path), { recursive: true });
|
|
708
1068
|
const temporary = output.path + '.aug-spec-tmp';
|
|
1069
|
+
let created = false;
|
|
709
1070
|
try {
|
|
710
|
-
writeFileSync(temporary, output.text, { flag: 'wx' });
|
|
1071
|
+
writeFileSync(temporary, output.text, { flag: 'wx', mode: output.kind === 'source-hint' ? lstatSync(output.path).mode : undefined });
|
|
1072
|
+
created = true;
|
|
711
1073
|
renameSync(temporary, output.path);
|
|
712
1074
|
}
|
|
713
1075
|
finally {
|
|
714
|
-
if (existsSync(temporary))
|
|
1076
|
+
if (created && existsSync(temporary))
|
|
715
1077
|
rmSync(temporary);
|
|
716
1078
|
}
|
|
717
1079
|
}
|
|
718
1080
|
for (const path of removed) {
|
|
719
1081
|
const text = readFileSync(path, 'utf8');
|
|
720
|
-
if (text.startsWith(generated) || text.startsWith(copied))
|
|
1082
|
+
if (previousDescriptors.has(path) || text.startsWith(generated) || text.startsWith(copied))
|
|
721
1083
|
rmSync(path);
|
|
722
1084
|
}
|
|
723
1085
|
if (options.manifest !== false) {
|
|
724
1086
|
mkdirSync(dirname(manifest), { recursive: true });
|
|
725
1087
|
if (!existsSync(manifest) || readFileSync(manifest, 'utf8') !== manifestText) {
|
|
726
1088
|
const temporary = manifest + '.aug-spec-tmp';
|
|
1089
|
+
let created = false;
|
|
727
1090
|
try {
|
|
728
1091
|
writeFileSync(temporary, manifestText, { flag: 'wx' });
|
|
1092
|
+
created = true;
|
|
729
1093
|
renameSync(temporary, manifest);
|
|
730
1094
|
}
|
|
731
1095
|
finally {
|
|
732
|
-
if (existsSync(temporary))
|
|
1096
|
+
if (created && existsSync(temporary))
|
|
733
1097
|
rmSync(temporary);
|
|
734
1098
|
}
|
|
735
1099
|
}
|