bc-atlas 0.11.0 → 0.13.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/.bca.example.json +0 -2
- package/CHANGELOG.md +121 -0
- package/README.md +22 -23
- package/THIRD_PARTY_NOTICES.md +23 -6
- package/package.json +10 -4
- package/src/analyzer.js +221 -13
- package/src/app/service.js +16 -2
- package/src/app/snapshot.js +4 -1
- package/src/app/ui/app.css +93 -7
- package/src/app/ui/app.js +425 -79
- package/src/app/workspace.js +62 -19
- package/src/blueprint/components.js +14 -4
- package/src/blueprint/evaluate.js +64 -18
- package/src/blueprint/index.js +3 -1
- package/src/blueprint/report.js +68 -3
- package/src/blueprint/resolutions.js +72 -0
- package/src/capabilities.js +10 -5
- package/src/commands/export.js +13 -5
- package/src/commands/refactor.js +4 -1
- package/src/commands/report.js +8 -18
- package/src/context-analysis.js +719 -0
- package/src/contract-analysis.js +372 -0
- package/src/d2.js +127 -81
- package/src/diagram/collapse.js +94 -0
- package/src/diagram/geometry.js +70 -0
- package/src/diagram/index.js +59 -0
- package/src/diagram/layout.js +191 -0
- package/src/diagram/metrics.js +4 -0
- package/src/diagram/pdf-font.js +133 -0
- package/src/diagram/pdf.js +205 -0
- package/src/diagram/png.js +36 -0
- package/src/diagram/svg.js +88 -0
- package/src/diagram/text.js +49 -0
- package/src/docs/export.js +75 -12
- package/src/docs/render/csv.js +6 -1
- package/src/docs/render/html.js +27 -13
- package/src/docs/task.js +70 -28
- package/src/graph/lenses/dependency.js +20 -3
- package/src/health.js +84 -13
- package/src/insights.js +107 -4
- package/src/knowledge/graph.js +213 -0
- package/src/knowledge/index.js +65 -0
- package/src/knowledge/ontology.js +105 -0
- package/src/knowledge/serialize.js +131 -0
- package/src/knowledge/vocabulary.js +210 -0
- package/src/output.js +12 -35
- package/src/refactor/move.js +14 -2
- package/src/relation-aggregation.js +18 -1
- package/src/resolver.js +1 -0
- package/src/sarif.js +48 -6
- package/src/views.js +10 -1
- package/vendor/README.md +9 -0
- package/vendor/fonts/LICENSE-LiberationSans.txt +101 -0
- package/vendor/fonts/LiberationSans-Bold.ttf +0 -0
- package/vendor/fonts/LiberationSans-Regular.ttf +0 -0
- package/src/svg.js +0 -67
package/.bca.example.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,127 @@ All notable changes to BC Atlas will be documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project
|
|
5
5
|
follows [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## 0.13.0 - 2026-11-04
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `bca check` flags interface methods whose enum implementation can complete
|
|
12
|
+
silently: an empty body, a constant `true` return, or a conditional path
|
|
13
|
+
with no error (`contract-fallback`). Without configuration these are
|
|
14
|
+
informational; `check.contractExpectations` names the interface methods
|
|
15
|
+
that must not fall back silently and turns them into warnings. Unresolved
|
|
16
|
+
delegation, symbol-only bodies, and ambiguous signatures stay unknown
|
|
17
|
+
instead of counting as safe. See [Configuration](./docs/configuration.md).
|
|
18
|
+
- `check.contexts` summarizes the responsibilities of selected objects:
|
|
19
|
+
reads, persistent and temporary writes, commits, resolved calls, and global
|
|
20
|
+
state, direct and up to `maxCallDepth` calls away. `roles`,
|
|
21
|
+
`lazyReadMethods`, and `forbid` turn them into policy warnings; bounded or
|
|
22
|
+
unresolved evidence is reported with its limitations.
|
|
23
|
+
- Architecture component selectors accept `file:` (one file, globs allowed),
|
|
24
|
+
more specific than a namespace, folder, or ID range.
|
|
25
|
+
- Enum bindings keep `DefaultImplementation` and `UnknownValueImplementation`
|
|
26
|
+
as evidence, and calls keep their arguments.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Health and architecture findings carry their basis (`configured-policy`,
|
|
31
|
+
`observed-code-fact`, or `heuristic` for a proposed architecture), a
|
|
32
|
+
primary location, all evidence, and limitations. Text and Markdown show up
|
|
33
|
+
to five locations; JSON and SARIF keep everything.
|
|
34
|
+
- Component cycles are explained by a representative path with the
|
|
35
|
+
dependencies behind each hop, and, where several providers call back into
|
|
36
|
+
the same orchestrator procedure, suggest extracting a shared service.
|
|
37
|
+
- `bca refactor move` defaults to the common parent folder of a component's
|
|
38
|
+
objects instead of the folder most of them are in.
|
|
39
|
+
- Records of pages and tables with `SourceTableTemporary` count as temporary,
|
|
40
|
+
and record parameters of unknown temporariness are no longer reported as
|
|
41
|
+
persistent writes.
|
|
42
|
+
- Release: one *Release* workflow publishes the npm package and the VS Code
|
|
43
|
+
extension under one version, after testing both, and attaches the `.vsix`
|
|
44
|
+
to the GitHub release.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Health reports no longer print `architecture origin: undefined`, and
|
|
49
|
+
Markdown evidence cells with `|` no longer break the table.
|
|
50
|
+
|
|
51
|
+
## 0.12.0 - 2026-10-29
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- `bca export --view knowledge-graph` writes the project as RDF with the BC
|
|
56
|
+
Atlas OWL ontology, for triple stores, graph databases, and ontology
|
|
57
|
+
editors: Turtle (`.ttl`), RDF/XML (`.rdf`, `.owl`), N-Triples (`.nt`), or
|
|
58
|
+
JSON-LD (`.jsonld`). Apps, namespaces, objects with fields, keys, and
|
|
59
|
+
procedures, every relation as a property (`bca:calls`, `bca:writes`, ...)
|
|
60
|
+
and as a `bca:Relation` with its evidence, objects outside the workspace,
|
|
61
|
+
architecture components, and violations. Stable IRIs (`urn:bc-atlas:...`, or
|
|
62
|
+
`--base-iri`), and `SOURCE_DATE_EPOCH` for reproducible files. See
|
|
63
|
+
[Knowledge graph](./docs/knowledge-graph.md).
|
|
64
|
+
- Knowledge graph: `--no-evidence` writes only the direct relation triples,
|
|
65
|
+
without a `bca:Relation` per relation; the Base Application's graph drops
|
|
66
|
+
from 4.4 million triples (270 MB of Turtle) to 1.1 million (78 MB).
|
|
67
|
+
- The BC Atlas ontology is published at its IRI,
|
|
68
|
+
`https://schulzoli.github.io/BC-Atlas/ontology`: a page with every class
|
|
69
|
+
and property (each term's IRI opens its row), and the ontology as
|
|
70
|
+
`ontology.ttl`, `.owl`, `.rdf`, `.nt`, and `.jsonld` next to it, for
|
|
71
|
+
Protégé and other tools that resolve IRIs. Exports point to it with
|
|
72
|
+
`rdfs:seeAlso`.
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
|
|
76
|
+
- App and VS Code panel: Diagrams and Docs work the same way. On the left, a
|
|
77
|
+
filterable list (the views; the overview, use cases, and scenarios) with
|
|
78
|
+
the settings below it; on the right, the selected item with its tools.
|
|
79
|
+
`J`/`K` and **‹**/**›** step through the list, **Enter** in the filter opens
|
|
80
|
+
the first match, and the address keeps the selection and the settings.
|
|
81
|
+
- Diagrams: views are listed with what they show, choices apply at once,
|
|
82
|
+
**PNG** saves the diagram besides **SVG** and **D2**, and a click on an
|
|
83
|
+
object opens its page.
|
|
84
|
+
- Diagrams are drawn by BC Atlas itself instead of D2: an ELK layered layout
|
|
85
|
+
with groups, right-angled routes (curves beyond 120 relations), labels, and
|
|
86
|
+
a legend, as SVG, PNG, or vector PDF. `--format png` and `--format pdf` no
|
|
87
|
+
longer need the `d2` executable, and every drawing still writes its D2
|
|
88
|
+
source next to it. Text is sized with the metrics of the bundled Liberation
|
|
89
|
+
Sans (Arial and Helvetica widths), so boxes fit in all three formats. Large
|
|
90
|
+
views are faster and far less tall (a 200-object project view: 5 instead of
|
|
91
|
+
19 seconds, 7499 × 6811 instead of 6112 × 25030 pixels).
|
|
92
|
+
- Diagrams page: hover an object to light up its relations and neighbors,
|
|
93
|
+
click it to open its page, find objects by name (**Enter** jumps to the next
|
|
94
|
+
match), and click a legend entry to hide that kind of relation.
|
|
95
|
+
- The npm package no longer installs `@terrastruct/d2` (58 MB); it uses
|
|
96
|
+
`elkjs` and `@resvg/resvg-wasm`. The VS Code extension shrinks from about 9
|
|
97
|
+
to 4.5 MB. The `layout` and `theme` settings of `.bca.json` (D2 options)
|
|
98
|
+
have no effect any more.
|
|
99
|
+
- Docs: the guides are part of the page instead of a page in a frame, one
|
|
100
|
+
overview, use case, or scenario at a time. Switch between user guide and
|
|
101
|
+
test case and between English and German; **Open test** opens the AL test
|
|
102
|
+
at its procedure; **Markdown**, **DITA**, **BPMN**, and **SVG** save the
|
|
103
|
+
page, **All as HTML** the whole documentation. Links between guides and
|
|
104
|
+
from a use case's process to its scenarios stay on the page.
|
|
105
|
+
- Large diagrams as an overview of their groups: every closed group is one
|
|
106
|
+
box with its object count and main object types, and the relations into,
|
|
107
|
+
out of, and between closed groups are bundled (one route per pair of groups
|
|
108
|
+
with the number of relations, per kind towards single objects). On the
|
|
109
|
+
Diagrams page, views with more than 120 objects or 250 relations start this
|
|
110
|
+
way; click a box to open its group, a group's name to close it, or use
|
|
111
|
+
**Open groups** and **Close groups**. Files of views with more than 1,500
|
|
112
|
+
objects are drawn as the overview instead of only writing the D2 source, so
|
|
113
|
+
the Base Application's project view (8,083 objects) now has a diagram.
|
|
114
|
+
- Diagrams page and the flows on the Docs page: on a dark theme the diagrams
|
|
115
|
+
turn dark too (saved files stay light).
|
|
116
|
+
- PDF diagrams with text outside Windows-1252 (Polish, Czech, Greek,
|
|
117
|
+
Cyrillic, ...) embed Liberation Sans, so the text shows and can be copied
|
|
118
|
+
and searched; other PDFs keep using Helvetica and stay small.
|
|
119
|
+
- The README's diagram images are rendered from the example SVGs by
|
|
120
|
+
`npm run examples`, and CI checks them with the other generated examples.
|
|
121
|
+
|
|
122
|
+
### Security
|
|
123
|
+
|
|
124
|
+
- The Azure DevOps test-case CSV (`--export ado-csv`) writes cells that start
|
|
125
|
+
with `=`, `+`, `-`, or `@` with a leading apostrophe, so Excel and Google
|
|
126
|
+
Sheets do not run them as formulas.
|
|
127
|
+
|
|
7
128
|
## 0.11.0 - 2026-10-29
|
|
8
129
|
|
|
9
130
|
### Changed
|
package/README.md
CHANGED
|
@@ -41,10 +41,10 @@ In VS Code, install the **BC Atlas** extension and open an AL project:
|
|
|
41
41
|
| **Get started** | `bca setup` | Guided setup in the terminal or through your AI agent (Claude Code, Copilot, Cursor): a starter `.bca.json`, features, Husky or Git hooks, GitHub Actions or Azure Pipelines, cron schedule, and sync. |
|
|
42
42
|
| **Analyze** | `bca check` | Health findings - cycles, forbidden dependencies, architecture violations, hot spots, diagnostics, and AL compiler and CodeCop/UICop/AppSourceCop findings - with a CI exit code, also as SARIF for code scanning. |
|
|
43
43
|
| | `bca architecture` | Find a target architecture (components, layers), list every dependency that blocks it with how to resolve it, and enforce it with a shrinking baseline. [Guide](./docs/clean-architecture.md) |
|
|
44
|
-
| **Export** | `bca export` | Everything the extension shows, as files: the BC Atlas pages as one offline HTML file to share (default), the Markdown report ([example](./examples/report/README.md)), the 3D explorer, one Markdown page per object, any of ten diagram views as D2, SVG, PNG, PDF, or JSON, with `--watch
|
|
44
|
+
| **Export** | `bca export` | Everything the extension shows, as files: the BC Atlas pages as one offline HTML file to share (default), the Markdown report ([example](./examples/report/README.md)), the 3D explorer, one Markdown page per object, any of ten diagram views as D2, SVG, PNG, PDF, or JSON, with `--watch`, and the project as a knowledge graph in RDF with an OWL ontology ([Turtle, RDF/XML, OWL, N-Triples, JSON-LD](./docs/knowledge-graph.md)). |
|
|
45
45
|
| | `bca docs` | User guides and test cases from AL UI tests (`[SCENARIO]`, `[GIVEN]`, `[WHEN]`, `[THEN]`) as Markdown, HTML, DITA 1.3, or Azure DevOps CSV, with real captions and translations, a flow diagram per scenario, and BPMN 2.0 processes per use case. [Example](./examples/docs/index.md) |
|
|
46
46
|
| **Integrate** | `bca run` | One entry point for Git hooks, schedules, and pipelines; verifies, stages, or publishes generated files. Also available as a [GitHub Action](./docs/automation-and-ci.md#github-actions). |
|
|
47
|
-
| | `bca update` | "BC Atlas 0.
|
|
47
|
+
| | `bca update` | "BC Atlas 0.14.0 is available": release notes, one-step update, and regenerated hooks, tasks, and pipelines. Dependabot pull requests on request. |
|
|
48
48
|
| | `bca mcp` | A Model Context Protocol server for AI agents; `bca help --format json` is the versioned command contract. |
|
|
49
49
|
|
|
50
50
|
Run `bca` for the same overview in your terminal and `bca help <command>` for
|
|
@@ -65,7 +65,7 @@ options and examples.
|
|
|
65
65
|
| `ui` | How do pages, actions, and source tables connect? | `bca export --view ui --focus "Sales Order" -o ui.svg` |
|
|
66
66
|
| `workflow` | What happens after an action or entry point? | `bca export --view workflow --entry action:Release -o flow.svg` |
|
|
67
67
|
|
|
68
|
-
In VS Code, the Diagrams page of the BC Atlas panel renders every view with zoom and pan.
|
|
68
|
+
In VS Code, the Diagrams page of the BC Atlas panel renders every view with zoom and pan; click an object to open it.
|
|
69
69
|
|
|
70
70
|
### Clean architecture
|
|
71
71
|
|
|
@@ -208,7 +208,7 @@ and update-check tools. `bca setup agent --agent claude|copilot|cursor` adds a
|
|
|
208
208
|
-> AL compiler: calls and diagnostics (src/al, when the toolchain is installed)
|
|
209
209
|
-> resolved model with evidence (resolver, call analysis, semantics)
|
|
210
210
|
-> view projection and insights (views, workflow, health)
|
|
211
|
-
->
|
|
211
|
+
-> SVG / PNG / PDF / D2 / JSON / Markdown / HTML (renderers)
|
|
212
212
|
-> the pages: one workspace, in VS Code (vscode/) or exported as one file
|
|
213
213
|
```
|
|
214
214
|
|
|
@@ -259,27 +259,25 @@ lists planned work.
|
|
|
259
259
|
|
|
260
260
|
### Publishing
|
|
261
261
|
|
|
262
|
-
Releases are published manually through the `
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
262
|
+
Releases are published manually through the `Release` workflow
|
|
263
|
+
(`.github/workflows/publish-npm.yml`). Set the version of both packages with
|
|
264
|
+
`npm run version:set -- <version>`, merge it, then run the workflow on `main`
|
|
265
|
+
with that exact version. It rejects mismatched, already published, or already
|
|
266
|
+
tagged versions, and tests the npm package and the VS Code extension, the
|
|
267
|
+
packaged `.vsix` included, before anything is published. Then it publishes the
|
|
268
|
+
npm package with the selected dist-tag and, unless switched off, the extension
|
|
269
|
+
to the Visual Studio Marketplace and, with the secret `OVSX_PAT`, to Open VSX.
|
|
270
|
+
Last, it tags the commit as `v<version>`, so
|
|
267
271
|
`uses: SchulzOli/BC-Atlas@v<version>` resolves, and creates a GitHub release
|
|
268
|
-
with the version's `CHANGELOG.md` section
|
|
272
|
+
with the version's `CHANGELOG.md` section and the `.vsix`.
|
|
273
|
+
|
|
274
|
+
No token is stored for either registry. npm authenticates through
|
|
269
275
|
[trusted publishing](https://docs.npmjs.com/trusted-publishers): the package's
|
|
270
276
|
trusted publisher on npmjs.com is this repository's `publish-npm.yml`
|
|
271
|
-
workflow
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
extension` workflow: set `version` in `vscode/package.json`, then run the
|
|
276
|
-
workflow with that version. It tests and packages the extension with the
|
|
277
|
-
current engine and publishes it to the Visual Studio Marketplace as the
|
|
278
|
-
publisher `SchulzOli`, signed in with Microsoft Entra ID through GitHub's OIDC
|
|
279
|
-
token (no Marketplace token is stored; see the
|
|
280
|
-
[launch checklist](https://github.com/SchulzOli/BC-Atlas/blob/main/vscode/marketing/README.md#launch-checklist)), and, with
|
|
281
|
-
the secret `OVSX_PAT`, to Open VSX. Every CI run uploads the `.vsix` as the
|
|
282
|
-
`bc-atlas-vsix` artifact.
|
|
277
|
+
workflow. The Marketplace publisher `SchulzOli` is signed in with Microsoft
|
|
278
|
+
Entra ID through GitHub's OIDC token in the `marketplace` environment (see the
|
|
279
|
+
[launch checklist](https://github.com/SchulzOli/BC-Atlas/blob/main/vscode/marketing/README.md#launch-checklist)).
|
|
280
|
+
Every CI run uploads the `.vsix` as the `bc-atlas-vsix` artifact.
|
|
283
281
|
|
|
284
282
|
## Contributing and support
|
|
285
283
|
|
|
@@ -291,5 +289,6 @@ Release notes are in [CHANGELOG.md](./CHANGELOG.md).
|
|
|
291
289
|
## License
|
|
292
290
|
|
|
293
291
|
[MIT](./LICENSE). Dependencies retain their own licenses: `tree-sitter-al` and
|
|
294
|
-
`web-tree-sitter` are MIT,
|
|
292
|
+
`web-tree-sitter` are MIT, elkjs is EPL-2.0, resvg is MPL-2.0, and the
|
|
293
|
+
Liberation Sans font is OFL-1.1. See
|
|
295
294
|
[THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -13,14 +13,31 @@ project.
|
|
|
13
13
|
Copyright Tree-sitter contributors. Licensed under the MIT License.
|
|
14
14
|
Source: <https://github.com/tree-sitter/tree-sitter>
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## ELK (elkjs)
|
|
17
17
|
|
|
18
|
-
Copyright
|
|
19
|
-
|
|
18
|
+
Copyright Kiel University and others. Licensed under the Eclipse Public License
|
|
19
|
+
2.0 (or, at your option, GPL-3.0-or-later). Source: <https://github.com/kieler/elkjs>
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
`elkjs` lays out the diagram views (layered layout, routes, and labels).
|
|
22
|
+
|
|
23
|
+
## resvg (@resvg/resvg-wasm)
|
|
24
|
+
|
|
25
|
+
Copyright the resvg authors. Licensed under the Mozilla Public License 2.0.
|
|
26
|
+
Source: <https://github.com/yisibl/resvg-js>
|
|
27
|
+
|
|
28
|
+
The resvg WebAssembly package is an npm dependency that turns diagrams into
|
|
29
|
+
PNG files.
|
|
30
|
+
|
|
31
|
+
## Liberation Sans
|
|
32
|
+
|
|
33
|
+
Digitized data copyright (c) 2010 Google Corporation; copyright (c) 2012 Red
|
|
34
|
+
Hat, Inc. Licensed under the SIL Open Font License 1.1. Source:
|
|
35
|
+
<https://github.com/liberationfonts/liberation-fonts>
|
|
36
|
+
|
|
37
|
+
`vendor/fonts/LiberationSans-Regular.ttf` and `LiberationSans-Bold.ttf` are
|
|
38
|
+
redistributed (license in `vendor/fonts/LICENSE-LiberationSans.txt`): PNG files
|
|
39
|
+
are rendered with them, and their glyph widths size the boxes of every
|
|
40
|
+
diagram.
|
|
24
41
|
|
|
25
42
|
## 3d-force-graph and three.js
|
|
26
43
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bc-atlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "BC Atlas maps Microsoft Dynamics 365 Business Central AL projects: architecture diagrams, health checks, reports, and documentation generated from source.",
|
|
5
5
|
"homepage": "https://github.com/SchulzOli/BC-Atlas#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"site:build": "vitepress build website && node ./scripts/site-assets.js",
|
|
39
39
|
"site:preview": "vitepress preview website",
|
|
40
40
|
"version:set": "node ./scripts/set-version.js",
|
|
41
|
-
"update:graph-vendor": "node ./scripts/build-graph-vendor.js"
|
|
41
|
+
"update:graph-vendor": "node ./scripts/build-graph-vendor.js",
|
|
42
|
+
"update:font-metrics": "node ./scripts/build-font-metrics.js"
|
|
42
43
|
},
|
|
43
44
|
"keywords": [
|
|
44
45
|
"al",
|
|
@@ -62,7 +63,8 @@
|
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
65
|
"@modelcontextprotocol/server": "^2.4.0",
|
|
65
|
-
"@
|
|
66
|
+
"@resvg/resvg-wasm": "^2.6.2",
|
|
67
|
+
"elkjs": "^0.12.1",
|
|
66
68
|
"fflate": "^0.8.3",
|
|
67
69
|
"graphology": "^0.26.0",
|
|
68
70
|
"graphology-communities-louvain": "^2.0.2",
|
|
@@ -71,10 +73,14 @@
|
|
|
71
73
|
"zod": "^4.6.7"
|
|
72
74
|
},
|
|
73
75
|
"devDependencies": {
|
|
74
|
-
"3d-force-graph": "^1.80.0",
|
|
75
76
|
"@modelcontextprotocol/client": "^2.4.0",
|
|
77
|
+
"3d-force-graph": "^1.80.0",
|
|
76
78
|
"bpmn-moddle": "^10.3.1",
|
|
77
79
|
"esbuild": "^0.28.2",
|
|
80
|
+
"jsonld": "^9.0.0",
|
|
81
|
+
"n3": "^2.13.12",
|
|
82
|
+
"opentype.js": "^2.0.0",
|
|
83
|
+
"rdfxml-streaming-parser": "^3.3.0",
|
|
78
84
|
"three": "^0.186.1",
|
|
79
85
|
"vitepress": "2.0.0-alpha.20"
|
|
80
86
|
}
|
package/src/analyzer.js
CHANGED
|
@@ -293,10 +293,18 @@ function enclosingMember(node, source, types) {
|
|
|
293
293
|
return undefined;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
function implementationRelations(value, node, source, add) {
|
|
296
|
+
function implementationRelations(property, value, node, source, add) {
|
|
297
|
+
const bindingKind = {
|
|
298
|
+
implementation: "explicit",
|
|
299
|
+
defaultimplementation: "default",
|
|
300
|
+
unknownvalueimplementation: "unknownValue"
|
|
301
|
+
}[property];
|
|
302
|
+
if (!bindingKind) return;
|
|
297
303
|
const identifier = String.raw`(?:"((?:""|[^"])*)"|([A-Za-z_][\w.]*))`;
|
|
298
304
|
const pattern = new RegExp(`${identifier}\\s*=\\s*${identifier}`, "gu");
|
|
299
|
-
const member =
|
|
305
|
+
const member = bindingKind === "explicit"
|
|
306
|
+
? enclosingMember(node, source, ["enum_value_declaration"])
|
|
307
|
+
: undefined;
|
|
300
308
|
for (const match of value.matchAll(pattern)) {
|
|
301
309
|
const contract = (match[1] ?? match[2]).replaceAll('""', '"');
|
|
302
310
|
const target = (match[3] ?? match[4]).replaceAll('""', '"');
|
|
@@ -304,6 +312,7 @@ function implementationRelations(value, node, source, add) {
|
|
|
304
312
|
target,
|
|
305
313
|
targetType: "codeunit",
|
|
306
314
|
kind: "selects",
|
|
315
|
+
bindingKind,
|
|
307
316
|
contract,
|
|
308
317
|
member
|
|
309
318
|
});
|
|
@@ -496,8 +505,8 @@ function referencesFor(objectNode, source, file) {
|
|
|
496
505
|
if (name === "includedpermissionsets") {
|
|
497
506
|
includedPermissionSetRelations(value, node, add);
|
|
498
507
|
}
|
|
499
|
-
if (
|
|
500
|
-
implementationRelations(value, node, source, add);
|
|
508
|
+
if (["implementation", "defaultimplementation", "unknownvalueimplementation"].includes(name)) {
|
|
509
|
+
implementationRelations(name, value, node, source, add);
|
|
501
510
|
}
|
|
502
511
|
if (name === "runobject") {
|
|
503
512
|
const match = value.match(
|
|
@@ -533,6 +542,9 @@ function callsFor(node, source, file) {
|
|
|
533
542
|
? functionNode.childForFieldName("object")
|
|
534
543
|
: undefined;
|
|
535
544
|
const argumentsNode = callNode.childForFieldName("arguments");
|
|
545
|
+
const argumentsList = argumentsNode?.namedChildren.map((argument) =>
|
|
546
|
+
source.slice(argument.startIndex, argument.endIndex).trim()
|
|
547
|
+
) ?? [];
|
|
536
548
|
return {
|
|
537
549
|
name: cleanName(source.slice(
|
|
538
550
|
(memberNode ?? functionNode).startIndex,
|
|
@@ -543,6 +555,7 @@ function callsFor(node, source, file) {
|
|
|
543
555
|
: undefined,
|
|
544
556
|
receiverCall: receiverNode?.type === "call_expression" ? describe(receiverNode) : undefined,
|
|
545
557
|
arity: argumentsNode?.namedChildCount ?? 0,
|
|
558
|
+
...(argumentsList.length ? { arguments: argumentsList } : {}),
|
|
546
559
|
scope: node.startPosition.row + 1,
|
|
547
560
|
location: locationFor(callNode, file)
|
|
548
561
|
};
|
|
@@ -610,6 +623,149 @@ function variableType(node, source) {
|
|
|
610
623
|
return declaredType(type);
|
|
611
624
|
}
|
|
612
625
|
|
|
626
|
+
function fallbackStatements(node, source, file, conditional = false) {
|
|
627
|
+
if (!node) return [];
|
|
628
|
+
if (node.type === "statement_block") {
|
|
629
|
+
return node.namedChildren.flatMap((child) =>
|
|
630
|
+
fallbackStatements(child, source, file, conditional)
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
if (node.type === "if_statement") {
|
|
634
|
+
return [{
|
|
635
|
+
kind: "conditional",
|
|
636
|
+
then: fallbackStatements(node.childForFieldName("then_branch"), source, file, true),
|
|
637
|
+
else: node.childForFieldName("else_branch")
|
|
638
|
+
? fallbackStatements(node.childForFieldName("else_branch"), source, file, true)
|
|
639
|
+
: null,
|
|
640
|
+
location: locationFor(node, file)
|
|
641
|
+
}];
|
|
642
|
+
}
|
|
643
|
+
if (node.type === "case_statement") {
|
|
644
|
+
const body = node.childForFieldName("body");
|
|
645
|
+
const branches = (body?.namedChildren ?? [])
|
|
646
|
+
.filter((child) => child.type === "case_branch")
|
|
647
|
+
.map((branch) => fallbackStatements(
|
|
648
|
+
branch.childForFieldName("body"),
|
|
649
|
+
source,
|
|
650
|
+
file,
|
|
651
|
+
true
|
|
652
|
+
));
|
|
653
|
+
const elseBranch = node.namedChildren.find((child) => child.type === "case_else_branch");
|
|
654
|
+
if (elseBranch) {
|
|
655
|
+
branches.push(elseBranch.namedChildren
|
|
656
|
+
.filter((child) => child.type !== "else_keyword")
|
|
657
|
+
.flatMap((child) => fallbackStatements(child, source, file, true)));
|
|
658
|
+
}
|
|
659
|
+
return [{
|
|
660
|
+
kind: "conditional",
|
|
661
|
+
branches,
|
|
662
|
+
hasImplicitEmptyPath: !elseBranch,
|
|
663
|
+
location: locationFor(node, file)
|
|
664
|
+
}];
|
|
665
|
+
}
|
|
666
|
+
if (["while_statement", "for_statement", "repeat_statement"].includes(node.type)) {
|
|
667
|
+
const body = node.childForFieldName("body");
|
|
668
|
+
return [{
|
|
669
|
+
kind: "conditional",
|
|
670
|
+
branches: [fallbackStatements(body, source, file, true)],
|
|
671
|
+
hasImplicitEmptyPath: node.type !== "repeat_statement",
|
|
672
|
+
location: locationFor(node, file)
|
|
673
|
+
}];
|
|
674
|
+
}
|
|
675
|
+
if (node.type === "call_expression") {
|
|
676
|
+
const functionNode = node.childForFieldName("function");
|
|
677
|
+
const memberNode = functionNode?.type === "member_expression"
|
|
678
|
+
? functionNode.childForFieldName("member")
|
|
679
|
+
: undefined;
|
|
680
|
+
const receiverNode = functionNode?.type === "member_expression"
|
|
681
|
+
? functionNode.childForFieldName("object")
|
|
682
|
+
: undefined;
|
|
683
|
+
const target = functionNode
|
|
684
|
+
? cleanName(source.slice(
|
|
685
|
+
(memberNode ?? functionNode).startIndex,
|
|
686
|
+
(memberNode ?? functionNode).endIndex
|
|
687
|
+
))
|
|
688
|
+
: undefined;
|
|
689
|
+
const argumentsNode = node.childForFieldName("arguments");
|
|
690
|
+
return [{
|
|
691
|
+
kind: "call",
|
|
692
|
+
target,
|
|
693
|
+
receiver: receiverNode
|
|
694
|
+
? cleanName(source.slice(receiverNode.startIndex, receiverNode.endIndex))
|
|
695
|
+
: undefined,
|
|
696
|
+
arity: argumentsNode?.namedChildCount ?? 0,
|
|
697
|
+
directError: !receiverNode && target?.toLocaleLowerCase("en-US") === "error",
|
|
698
|
+
conditional,
|
|
699
|
+
location: locationFor(node, file)
|
|
700
|
+
}];
|
|
701
|
+
}
|
|
702
|
+
if (node.type === "exit_statement") {
|
|
703
|
+
const returnNode = node.childForFieldName("return_value");
|
|
704
|
+
return [{
|
|
705
|
+
kind: "return",
|
|
706
|
+
value: returnNode ? source.slice(returnNode.startIndex, returnNode.endIndex).trim() : undefined,
|
|
707
|
+
constant: Boolean(returnNode && [
|
|
708
|
+
"boolean",
|
|
709
|
+
"integer",
|
|
710
|
+
"decimal",
|
|
711
|
+
"string_literal"
|
|
712
|
+
].includes(returnNode.type)),
|
|
713
|
+
conditional,
|
|
714
|
+
location: locationFor(node, file)
|
|
715
|
+
}];
|
|
716
|
+
}
|
|
717
|
+
if (node.type === "assignment_statement") {
|
|
718
|
+
const targetNode = node.childForFieldName("left");
|
|
719
|
+
return [{
|
|
720
|
+
kind: "assignment",
|
|
721
|
+
target: targetNode
|
|
722
|
+
? cleanName(source.slice(targetNode.startIndex, targetNode.endIndex))
|
|
723
|
+
: undefined,
|
|
724
|
+
conditional,
|
|
725
|
+
location: locationFor(node, file)
|
|
726
|
+
}];
|
|
727
|
+
}
|
|
728
|
+
return [{
|
|
729
|
+
kind: "other",
|
|
730
|
+
conditional,
|
|
731
|
+
location: locationFor(node, file)
|
|
732
|
+
}];
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function fallbackFactsFor(node, source, file, hasBody) {
|
|
736
|
+
if (!hasBody) return undefined;
|
|
737
|
+
const codeBlock = node.namedChildren.find((child) =>
|
|
738
|
+
["code_block", "statement_block"].includes(child.type)
|
|
739
|
+
);
|
|
740
|
+
const body = codeBlock?.childForFieldName("body") ??
|
|
741
|
+
codeBlock?.namedChildren.find((child) => child.type === "statement_block");
|
|
742
|
+
return {
|
|
743
|
+
complete: !node.hasError && !codeBlock?.hasError,
|
|
744
|
+
statements: fallbackStatements(body, source, file),
|
|
745
|
+
...((node.hasError || codeBlock?.hasError)
|
|
746
|
+
? { limitations: ["The AL parser reported errors in this procedure body."] }
|
|
747
|
+
: {})
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function resolveFallbackCalls(statements, procedures) {
|
|
752
|
+
for (const statement of statements) {
|
|
753
|
+
if (statement.kind === "conditional") {
|
|
754
|
+
if (statement.then) resolveFallbackCalls(statement.then, procedures);
|
|
755
|
+
if (statement.else) resolveFallbackCalls(statement.else, procedures);
|
|
756
|
+
for (const branch of statement.branches ?? []) {
|
|
757
|
+
resolveFallbackCalls(branch, procedures);
|
|
758
|
+
}
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (statement.kind !== "call" || statement.directError) continue;
|
|
762
|
+
const candidates = procedures.get(`${statement.target?.toLocaleLowerCase("en-US")}:${statement.arity}`) ?? [];
|
|
763
|
+
const target = !statement.receiver && candidates.length === 1 ? candidates[0] : undefined;
|
|
764
|
+
statement.resolved = Boolean(target?.fallbackFacts?.complete);
|
|
765
|
+
if (target) statement.targetSignature = target.signature;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
613
769
|
function membersFor(objectNode, source, file) {
|
|
614
770
|
const procedures = [];
|
|
615
771
|
const fields = [];
|
|
@@ -624,18 +780,24 @@ function membersFor(objectNode, source, file) {
|
|
|
624
780
|
const name = fieldText(node, "name", source);
|
|
625
781
|
if (name) {
|
|
626
782
|
const returnTypeNode = node.childForFieldName("return_type");
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
783
|
+
const header = procedureHeader(node, source);
|
|
784
|
+
const returnTypeText = returnTypeNode
|
|
785
|
+
? source.slice(returnTypeNode.startIndex, returnTypeNode.endIndex).trim()
|
|
786
|
+
: header.match(/\)\s*:\s*([^;\r\n]+)$/u)?.[1]?.trim();
|
|
787
|
+
const returnType = declaredType(returnTypeText);
|
|
630
788
|
const attributes = [
|
|
631
789
|
...node.namedChildren
|
|
632
790
|
.filter((child) => child.type === "attribute_item")
|
|
633
791
|
.map((child) => source.slice(child.startIndex, child.endIndex)),
|
|
634
792
|
...attributesFor(node, source)
|
|
635
793
|
];
|
|
794
|
+
const hasBody = node.namedChildren.some((child) =>
|
|
795
|
+
["code_block", "statement_block"].includes(child.type)
|
|
796
|
+
);
|
|
797
|
+
const fallbackFacts = fallbackFactsFor(node, source, file, hasBody);
|
|
636
798
|
procedures.push({
|
|
637
799
|
name,
|
|
638
|
-
header
|
|
800
|
+
header,
|
|
639
801
|
visibility: node.type === "trigger_declaration"
|
|
640
802
|
? undefined
|
|
641
803
|
: fieldText(node, "modifier", source)?.toLowerCase() ?? "public",
|
|
@@ -647,6 +809,8 @@ function membersFor(objectNode, source, file) {
|
|
|
647
809
|
: attributes.some((attribute) => /\[(?:Integration|Business)Event\b/iu.test(attribute))
|
|
648
810
|
? "event"
|
|
649
811
|
: "procedure",
|
|
812
|
+
hasBody,
|
|
813
|
+
...(fallbackFacts ? { fallbackFacts } : {}),
|
|
650
814
|
location: locationFor(node, file),
|
|
651
815
|
calls: callsFor(node, source, file),
|
|
652
816
|
attributes,
|
|
@@ -825,6 +989,18 @@ function membersFor(objectNode, source, file) {
|
|
|
825
989
|
.map(({ order, location }) => ({ kind: "commit", order, location }));
|
|
826
990
|
procedure.transactionSegments = procedure.transactionBoundaries.length + 1;
|
|
827
991
|
}
|
|
992
|
+
const localProcedures = new Map();
|
|
993
|
+
for (const procedure of procedures) {
|
|
994
|
+
const key = `${procedure.name.toLocaleLowerCase("en-US")}:${procedure.arity}`;
|
|
995
|
+
const candidates = localProcedures.get(key) ?? [];
|
|
996
|
+
candidates.push(procedure);
|
|
997
|
+
localProcedures.set(key, candidates);
|
|
998
|
+
}
|
|
999
|
+
for (const procedure of procedures) {
|
|
1000
|
+
if (procedure.fallbackFacts) {
|
|
1001
|
+
resolveFallbackCalls(procedure.fallbackFacts.statements, localProcedures);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
828
1004
|
const transactionBoundaries = procedures.flatMap((procedure) =>
|
|
829
1005
|
procedure.transactionBoundaries.map((boundary) => ({
|
|
830
1006
|
...boundary,
|
|
@@ -854,8 +1030,8 @@ function dataOperationRelations(members, object) {
|
|
|
854
1030
|
.map((variable) => [variable.name.toLowerCase(), variable])
|
|
855
1031
|
);
|
|
856
1032
|
if (sourceTable) {
|
|
857
|
-
variables.set("rec", { target: sourceTable, temporary:
|
|
858
|
-
variables.set("xrec", { target: sourceTable, temporary:
|
|
1033
|
+
variables.set("rec", { target: sourceTable, temporary: object.sourceTableTemporary });
|
|
1034
|
+
variables.set("xrec", { target: sourceTable, temporary: object.sourceTableTemporary });
|
|
859
1035
|
}
|
|
860
1036
|
let transactionSegment = 1;
|
|
861
1037
|
for (const call of procedure.calls) {
|
|
@@ -874,7 +1050,10 @@ function dataOperationRelations(members, object) {
|
|
|
874
1050
|
kind,
|
|
875
1051
|
relationClass: "runtime",
|
|
876
1052
|
operation: call.name,
|
|
877
|
-
|
|
1053
|
+
recordVariable: call.receiver,
|
|
1054
|
+
temporary: variable.parameter && variable.temporary !== true
|
|
1055
|
+
? undefined
|
|
1056
|
+
: variable.temporary,
|
|
878
1057
|
member: procedure.signature,
|
|
879
1058
|
sourceProcedure: procedure.signature,
|
|
880
1059
|
transactionSegment,
|
|
@@ -885,6 +1064,24 @@ function dataOperationRelations(members, object) {
|
|
|
885
1064
|
return relations;
|
|
886
1065
|
}
|
|
887
1066
|
|
|
1067
|
+
function subscriberRelations(procedures) {
|
|
1068
|
+
const relations = [];
|
|
1069
|
+
for (const procedure of procedures) {
|
|
1070
|
+
for (const attribute of procedure.attributes ?? []) {
|
|
1071
|
+
const match = attribute.match(/^\[\s*EventSubscriber\s*\(\s*ObjectType::(Codeunit|Table|Page|Report|XmlPort)\s*,\s*(?:Codeunit|Database|Page|Report|XmlPort)::\s*(?:"((?:""|[^"])*)"|([^,\]\s]+))/iu);
|
|
1072
|
+
if (!match) continue;
|
|
1073
|
+
relations.push({
|
|
1074
|
+
target: (match[2] ?? match[3]).replaceAll('""', '"'),
|
|
1075
|
+
targetType: match[1].toLowerCase(),
|
|
1076
|
+
kind: "subscribes",
|
|
1077
|
+
member: procedure.name,
|
|
1078
|
+
location: procedure.location
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return relations;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
888
1085
|
function helperDataOperationRelations(members, directRelations) {
|
|
889
1086
|
const procedures = new Map();
|
|
890
1087
|
for (const procedure of members.procedures) {
|
|
@@ -971,7 +1168,18 @@ async function objectsFromSource(source, file) {
|
|
|
971
1168
|
});
|
|
972
1169
|
}
|
|
973
1170
|
const members = membersFor(node, source, file);
|
|
974
|
-
const
|
|
1171
|
+
const properties = propertiesFor(node, source);
|
|
1172
|
+
const sourceTableTemporary = /^true$/iu.test(
|
|
1173
|
+
propertyValueFor(node, source, "sourcetabletemporary") ?? "false"
|
|
1174
|
+
);
|
|
1175
|
+
relations.push(...subscriberRelations(members.procedures));
|
|
1176
|
+
const dataRelations = dataOperationRelations(members, {
|
|
1177
|
+
type,
|
|
1178
|
+
name,
|
|
1179
|
+
base,
|
|
1180
|
+
relations,
|
|
1181
|
+
sourceTableTemporary
|
|
1182
|
+
});
|
|
975
1183
|
relations.push(...dataRelations, ...helperDataOperationRelations(members, dataRelations));
|
|
976
1184
|
|
|
977
1185
|
const assignableValue = type === "permissionset"
|
|
@@ -987,7 +1195,7 @@ async function objectsFromSource(source, file) {
|
|
|
987
1195
|
namespace,
|
|
988
1196
|
usings,
|
|
989
1197
|
file,
|
|
990
|
-
properties
|
|
1198
|
+
properties,
|
|
991
1199
|
relations,
|
|
992
1200
|
procedures: members.procedures,
|
|
993
1201
|
fields: members.fields,
|