@ecoma-io/archkeep 0.14.0 → 0.16.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.
Files changed (68) hide show
  1. package/README.md +11 -5
  2. package/cli.mjs +571 -61
  3. package/commands.mjs +57 -0
  4. package/lsp.mjs +15 -2
  5. package/package.json +8 -2
  6. package/src/analysis/analyze.mjs +15 -0
  7. package/src/analysis/contract.md +36 -18
  8. package/src/analysis/csharp.mjs +485 -0
  9. package/src/analysis/dotnet/csproj.mjs +380 -0
  10. package/src/analysis/dotnet/mask.mjs +178 -0
  11. package/src/analysis/dotnet/namespaces.mjs +172 -0
  12. package/src/analysis/dotnet/resolve.mjs +89 -0
  13. package/src/analysis/go.mjs +289 -5
  14. package/src/analysis/java.mjs +329 -0
  15. package/src/analysis/jvm/gradle.mjs +545 -0
  16. package/src/analysis/jvm/mask.mjs +170 -0
  17. package/src/analysis/jvm/maven.mjs +612 -0
  18. package/src/analysis/jvm/packages.mjs +209 -0
  19. package/src/analysis/jvm/resolve.mjs +139 -0
  20. package/src/analysis/kotlin.mjs +210 -0
  21. package/src/analysis/manifest-util.mjs +30 -0
  22. package/src/analysis/python.mjs +3 -2
  23. package/src/analysis/registry.mjs +11 -0
  24. package/src/analysis/rust.mjs +171 -17
  25. package/src/analysis/source-util.mjs +155 -6
  26. package/src/analysis/typescript.mjs +11 -3
  27. package/src/commands/README.md +52 -1
  28. package/src/commands/change-intent.mjs +461 -0
  29. package/src/commands/change.mjs +612 -0
  30. package/src/commands/check.mjs +2 -1
  31. package/src/commands/context.mjs +124 -16
  32. package/src/commands/custom-rules.mjs +286 -2
  33. package/src/commands/delta-classify.mjs +195 -33
  34. package/src/commands/delta-snapshot.mjs +156 -1
  35. package/src/commands/delta.mjs +142 -17
  36. package/src/commands/diff.mjs +41 -13
  37. package/src/commands/evolution.mjs +473 -0
  38. package/src/commands/history.mjs +130 -103
  39. package/src/commands/policy.mjs +57 -0
  40. package/src/commands/provenance.mjs +7 -44
  41. package/src/commands/rules.mjs +775 -0
  42. package/src/commands/trajectory.mjs +437 -0
  43. package/src/governance/profile-registry.mjs +0 -1
  44. package/src/graph/create-dependencies.mjs +138 -15
  45. package/src/lsp/diagnose.mjs +1 -1
  46. package/src/lsp/server.mjs +97 -1
  47. package/src/lsp/workspace-index.mjs +106 -15
  48. package/src/options.mjs +30 -7
  49. package/src/path-util.mjs +40 -0
  50. package/src/process.mjs +10 -1
  51. package/src/providers/moon.mjs +287 -36
  52. package/src/providers/native/differential.fixtures.mjs +32 -6
  53. package/src/providers/native/discover.mjs +83 -4
  54. package/src/providers/native/graph.mjs +58 -0
  55. package/src/providers/native/model.mjs +59 -1
  56. package/src/report/change-text.mjs +148 -0
  57. package/src/report/delta-text.mjs +82 -1
  58. package/src/report/evolution-text.mjs +83 -0
  59. package/src/report/history-text.mjs +4 -114
  60. package/src/report/sarif.mjs +255 -0
  61. package/src/report/snapshot-text.mjs +123 -0
  62. package/src/report/trajectory-text.mjs +143 -0
  63. package/src/rules/index.mjs +21 -6
  64. package/src/rules/reachability.mjs +2 -0
  65. package/src/rules/tags.mjs +7 -5
  66. package/src/rules/topology.mjs +5 -3
  67. package/src/tsconfig-paths.mjs +3 -2
  68. package/src/workspace.mjs +115 -23
@@ -0,0 +1,170 @@
1
+ /**
2
+ * The JVM lexical mask — the shared scanner both source frontends and the
3
+ * package index read through, because Java and Kotlin disagree about exactly
4
+ * one thing a scanner has to know (whether block comments nest) and agree
5
+ * about everything else that matters here.
6
+ *
7
+ * Like `../../go.mjs`'s `maskGoComments`, the mask BLANKS rather than deletes:
8
+ * the result is byte-for-byte the same length with every line break in
9
+ * place, so an offset into the mask is the same offset into the original
10
+ * and `positionAt` reports the line the reader sees. A regex over the mask
11
+ * cannot see a comment's or a string literal's contents, which is the point:
12
+ * a commented-out import or a text block quoting one is text, not a written
13
+ * import, and counting it would report a violation naming code nobody wrote.
14
+ *
15
+ * Why one scanner, parameterized, rather than two copies: the package index
16
+ * (`./packages.mjs`) reads BOTH extensions from one tree — a mixed
17
+ * Java/Kotlin module compiles into one package namespace, so a `.java`
18
+ * import may resolve into a package only a `.kt` file declares. The
19
+ * dialects' literal grammars differ in exactly the places this table pins,
20
+ * so the parameterization IS the honesty; folding the two into one average
21
+ * grammar would misread one of them.
22
+ *
23
+ * Per dialect, the literals and comments this scanner knows:
24
+ *
25
+ * | Token | Java | Kotlin |
26
+ * |------------------|-------------------------------|----------------------------|
27
+ * | line comment | two slashes to end of line | two slashes to end of line |
28
+ * | block comment | slash-star .. star-slash,
29
+ * | | does NOT nest | NESTS |
30
+ * | string | `"…"` with `\` escapes | `"…"` with `\` escapes |
31
+ * | triple-quoted | text block `"""…"""`, escaped | raw string `"""…"""`,
32
+ * | | | NO escapes |
33
+ * | char | `'…'` | `'…'` |
34
+ *
35
+ * The nesting difference is load-bearing in the loud direction. Treating a
36
+ * Java block comment as nesting would keep masking past its first closer
37
+ * when a comment's prose holds a second opener — javadoc quoting a code
38
+ * snippet is ordinary Java — and every real import below the swallowed
39
+ * region would vanish: the silent direction, byte-for-byte identical to a
40
+ * clean file. Scanning each dialect by its own rule keeps the worst case a
41
+ * spurious record naming text the file really contains, never a missed
42
+ * import.
43
+ */
44
+
45
+ /**
46
+ * Every character of `text` except its line breaks, replaced by a space.
47
+ */
48
+ const blankOut = (text) => text.replace(/[^\n]/g, " ");
49
+
50
+ /** Escape-walk over a `"…"` / `'…'` literal body: `\` skips the next char. */
51
+ const escapedStringLength = (text, start, quote) => {
52
+ let at = start + 1;
53
+ while (at < text.length && text[at] !== quote && text[at] !== "\n") {
54
+ at += text[at] === "\\" ? 2 : 1;
55
+ }
56
+ return Math.min(text[at] === quote ? at + 1 : at, text.length) - start;
57
+ };
58
+
59
+ /** Length of a triple-quoted literal opened at `start`. */
60
+ const tripleQuotedLength = (text, start, escapes) => {
61
+ const terminator = '"""';
62
+ let at = start + 3;
63
+ while (at < text.length) {
64
+ const close = text.indexOf(terminator, at);
65
+ if (close === -1) return text.length - start;
66
+ if (!escapes) return close + 3 - start;
67
+ // Count the backslashes immediately before the candidate terminator: an
68
+ // even run leaves all three quotes unescaped (the literal ends), an odd
69
+ // run escapes the first of them, leaving two — not a terminator.
70
+ let slashes = 0;
71
+ for (let back = close - 1; back >= at && text[back] === "\\"; back--) slashes++;
72
+ if (slashes % 2 === 0) return close + 3 - start;
73
+ at = close + 3;
74
+ }
75
+ return text.length - start;
76
+ };
77
+
78
+ /** Length of the comment (or unterminated run) opened at `start`. */
79
+ const blockCommentLength = (text, start, nests) => {
80
+ let depth = 1;
81
+ let at = start + 2;
82
+ while (at < text.length) {
83
+ if (text.startsWith("/*", at)) {
84
+ if (!nests) {
85
+ // Java: a second opener inside the open comment is prose; skip it
86
+ // without counting, so the first closer still ends the comment.
87
+ at += 2;
88
+ continue;
89
+ }
90
+ depth++;
91
+ at += 2;
92
+ continue;
93
+ }
94
+ if (text.startsWith("*/", at)) {
95
+ depth--;
96
+ at += 2;
97
+ if (depth === 0) return at - start;
98
+ continue;
99
+ }
100
+ at++;
101
+ }
102
+ return text.length - start;
103
+ };
104
+
105
+ const LEXICAL_START = /\/\/|\/\*|"""|["']/g;
106
+
107
+ /**
108
+ * `sourceText` with every comment AND literal blanked out, same length, line
109
+ * breaks in place. See the module header for why the dialect decides nesting
110
+ * and escaping rather than one average grammar serving both.
111
+ *
112
+ * Literals are blanked here where Go's mask keeps its raw strings intact —
113
+ * the opposite choice on purpose. A Go import path IS a string literal, so a
114
+ * Go mask that ate strings would have nothing left to read; a JVM import is
115
+ * bare words after the keyword, and every literal body (a text block quoting
116
+ * a tutorial's `package` line, a string holding a class name) can only plant
117
+ * spurious declarations into text that scans like code.
118
+ *
119
+ * @param {string} sourceText
120
+ * @param {{ nests: boolean, tripleEscapes: boolean }} dialect
121
+ * @returns {string} Same length as `sourceText`.
122
+ */
123
+ function maskJvmDialect(sourceText, dialect) {
124
+ const scan = new RegExp(LEXICAL_START.source, "g");
125
+ let masked = "";
126
+ let copied = 0;
127
+ let match;
128
+ while ((match = scan.exec(sourceText)) !== null) {
129
+ const start = match.index;
130
+ let end;
131
+ if (match[0] === "//") {
132
+ const newline = sourceText.indexOf("\n", start);
133
+ end = newline === -1 ? sourceText.length : newline;
134
+ } else if (match[0] === "/*") {
135
+ end = start + blockCommentLength(sourceText, start, dialect.nests);
136
+ } else if (match[0] === '"""') {
137
+ end = start + tripleQuotedLength(sourceText, start, dialect.tripleEscapes);
138
+ } else {
139
+ end = start + escapedStringLength(sourceText, start, match[0]);
140
+ }
141
+ // Newlines survive everywhere; every other byte of the span goes.
142
+ masked += sourceText.slice(copied, start) + blankOut(sourceText.slice(start, end));
143
+ copied = end;
144
+ scan.lastIndex = end;
145
+ }
146
+ return masked + sourceText.slice(copied);
147
+ }
148
+
149
+ /**
150
+ * `javaText` with every Java comment and literal blanked out — see the
151
+ * module header.
152
+ *
153
+ * @param {string} javaText
154
+ * @returns {string} Same length as `javaText`.
155
+ */
156
+ export const maskJavaComments = (javaText) =>
157
+ maskJvmDialect(javaText, { nests: false, tripleEscapes: true });
158
+
159
+ /**
160
+ * `kotlinText` with every Kotlin comment and literal blanked out — see the module
161
+ * header. Kept beside `maskJavaComments` from the day the package index
162
+ * lands, because the index reads `.kt` sources too: a mixed Java/Kotlin
163
+ * module compiles into one package namespace, and a `.java` import may
164
+ * reach a package only a `.kt` file declares.
165
+ *
166
+ * @param {string} kotlinText
167
+ * @returns {string} Same length as `kotlinText`.
168
+ */
169
+ export const maskKotlinComments = (kotlinText) =>
170
+ maskJvmDialect(kotlinText, { nests: true, tripleEscapes: false });