@ecoma-io/archkeep 0.15.0 → 0.16.1
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 +3 -3
- package/cli.mjs +126 -4
- package/commands.mjs +6 -0
- package/lsp.mjs +15 -2
- package/package.json +6 -2
- package/src/analysis/analyze.mjs +15 -0
- package/src/analysis/contract.md +36 -18
- package/src/analysis/csharp.mjs +514 -0
- package/src/analysis/dotnet/csproj.mjs +380 -0
- package/src/analysis/dotnet/mask.mjs +178 -0
- package/src/analysis/dotnet/namespaces.mjs +172 -0
- package/src/analysis/dotnet/resolve.mjs +89 -0
- package/src/analysis/go.mjs +303 -5
- package/src/analysis/java.mjs +329 -0
- package/src/analysis/jvm/gradle.mjs +545 -0
- package/src/analysis/jvm/mask.mjs +170 -0
- package/src/analysis/jvm/maven.mjs +612 -0
- package/src/analysis/jvm/packages.mjs +209 -0
- package/src/analysis/jvm/resolve.mjs +139 -0
- package/src/analysis/kotlin.mjs +210 -0
- package/src/analysis/manifest-util.mjs +30 -0
- package/src/analysis/python.mjs +3 -2
- package/src/analysis/registry.mjs +11 -0
- package/src/analysis/rust.mjs +171 -17
- package/src/analysis/source-util.mjs +155 -6
- package/src/analysis/typescript.mjs +9 -2
- package/src/commands/context.mjs +84 -14
- package/src/commands/provenance.mjs +7 -44
- package/src/commands/rules.mjs +775 -0
- package/src/governance/profile-registry.mjs +0 -1
- package/src/graph/create-dependencies.mjs +138 -15
- package/src/lsp/diagnose.mjs +1 -1
- package/src/lsp/server.mjs +97 -1
- package/src/lsp/workspace-index.mjs +106 -15
- package/src/options.mjs +30 -7
- package/src/process.mjs +10 -1
- package/src/providers/moon.mjs +287 -36
- package/src/providers/native/differential.fixtures.mjs +32 -6
- package/src/providers/native/discover.mjs +83 -4
- package/src/providers/native/graph.mjs +58 -0
- package/src/providers/native/model.mjs +59 -1
- package/src/rules/index.mjs +21 -6
- package/src/rules/reachability.mjs +2 -0
- package/src/rules/tags.mjs +7 -5
- package/src/rules/topology.mjs +5 -3
- 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 });
|