@ecoma-io/archkeep 0.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecoma-io/archkeep",
3
- "version": "0.16.0",
3
+ "version": "0.16.1",
4
4
  "description": "Architecture enforcement for polyglot repositories — dependency graphs and module boundaries for Go, Rust, Python, TypeScript, JavaScript, Vue, Java and Kotlin",
5
5
  "keywords": [
6
6
  "architecture",
@@ -70,6 +70,11 @@
70
70
  * same class of limit Kotlin's backtick segments pin.
71
71
  * - An **unterminated directive** (no `;` before end of line) is not a
72
72
  * complete directive and is not read.
73
+ * - A **statement whose initializer opens but never closes** (`using var x =
74
+ * new F {`, the `;` never arriving) is neither read nor flagged — the `{`
75
+ * behind the resource's `=` is the statement's own (#469), the same
76
+ * silence a `using (r) {` block has always held. The malformation scan
77
+ * judges directives only.
73
78
  */
74
79
  import { csharpNamespaceIndex } from "./dotnet/namespaces.mjs";
75
80
  import { maskCSharpComments } from "./dotnet/mask.mjs";
@@ -130,6 +135,13 @@ const CS_EXTERN_ALIAS = new RegExp(
130
135
  /** Exactly one identifier followed by `=`: the alias form. */
131
136
  const ALIAS_FORM = new RegExp(String.raw`^(${SEG})[ \t]*=[ \t]*(.+)$`, "su");
132
137
 
138
+ /**
139
+ * One bare identifier, optionally verbatim (`@class`): an alias's own name.
140
+ * The malformation scan reads it backwards — the text before an initializer
141
+ * `=` is an alias's name only when it is NOT this shape.
142
+ */
143
+ const ALIAS_NAME = new RegExp(String.raw`^@?${SEG}$`, "u");
144
+
133
145
  /** A dotted name, optionally behind the `global::` qualifier: the plain form. */
134
146
  const PLAIN_FORM = new RegExp(String.raw`^(?:global::)?(${DOTTED_NAME})$`, "u");
135
147
 
@@ -262,10 +274,17 @@ export function parseCSharpDirectiveSites(csharpText) {
262
274
  * from one the file truncates — a failed write, a merge marker left
263
275
  * mid-directive — which used to parse as zero directive sites with no
264
276
  * failure, byte-for-byte identical to a file that imports nothing (#419).
265
- * A `(` right after the
266
- * keyword is the using-STATEMENT family `using (var s = f()) { }`,
267
- * `using (x);`, and the `using var x = f();` declaration whose body starts
268
- * `var` never a directive; a statement's `;` may arrive inside its own
277
+ * Statement shapes hold no opinion at all, by two markers the text shows
278
+ * before any terminator arrives: a `(` right after the keyword is the
279
+ * parenthesized family `using (var s = f()) { … }`, `using (x);` and an
280
+ * `=` between the keyword and the walk's first `{` is the declaration family
281
+ * (#469) whenever MORE than one bare identifier precedes it — `var writer`,
282
+ * `Dictionary<string, int> map`, every declaration spelling — because a
283
+ * brace behind a resource's own `=` is that statement's initializer, while
284
+ * the first `{` behind a directive can only be the file's truncation. One
285
+ * bare identifier before the `=` is an ALIAS's own name (`using Pair = …`),
286
+ * the brace then belongs to whatever follows, and the truncated alias stays
287
+ * loud. A statement's `;` may arrive inside its own
269
288
  * block, so the scan has no opinion there. One silence the rule keeps: a
270
289
  * missing `;` that a LATER declaration supplies its own (`extern alias X`
271
290
  * then `namespace Shop.App;`) is not seen — the walk reads the next
@@ -324,11 +343,21 @@ export function csharpDirectiveMalformations(csharpText) {
324
343
  if (source[at] === "(") continue;
325
344
  const next = usingTerminatorAfter(at);
326
345
  if (next === undefined || next[0] === "{") {
327
- flag(
328
- m.index + m[0].indexOf("using"),
329
- "using",
330
- "a `using` directive never reaches its `;` — the file is truncated or malformed, so its imports cannot be read",
331
- );
346
+ // The declaration family's initializer `=` (#469) — argued beside the
347
+ // openers above: an `=` whose prefix is more than one bare identifier
348
+ // is a resource's own, and the brace with it. One bare identifier is
349
+ // an alias's own name, the brace belongs to whatever follows, and the
350
+ // truncated alias stays loud.
351
+ const eq = next !== undefined ? source.indexOf("=", at) : -1;
352
+ const initializer =
353
+ eq !== -1 && eq < next.index && !ALIAS_NAME.test(source.slice(at, eq).trim());
354
+ if (next === undefined || !initializer) {
355
+ flag(
356
+ m.index + m[0].indexOf("using"),
357
+ "using",
358
+ "a `using` directive never reaches its `;` — the file is truncated or malformed, so its imports cannot be read",
359
+ );
360
+ }
332
361
  }
333
362
  }
334
363
  const externTerminatorAfter = terminatorAfter();
@@ -44,6 +44,13 @@
44
44
  * quote closed on a later line, a `)` supplied from a function body below —
45
45
  * produce a record today and stay the documented parse limits above.
46
46
  *
47
+ * One misread answers neither record nor failure, named as a limit rather
48
+ * than fixed (#468's neighbourhood): a bare `import` whose line ends with
49
+ * no path and whose `(` arrives from the statement BELOW it — `import`
50
+ * alone, then `const (` opening the next — is read as that block, and the
51
+ * doubly-broken file stays silent. Rejecting it needs a same-line rule
52
+ * that would flag the legal `import` newline `"path"` continuation.
53
+ *
47
54
  * ## An unreadable go.mod refuses, it does not skip (#405)
48
55
  *
49
56
  * `parseGoModulePath` parses content; it never sees a failed read. Reading a
@@ -310,7 +317,14 @@ export function goImportMalformations(goText) {
310
317
  // close just found rather than rescanning the text before it.
311
318
  let lastClose = 0;
312
319
  let unclosedSince = -1;
313
- for (const m of source.matchAll(/(?:^|;)[ \t]*import/gm)) {
320
+ // `import` must be the keyword, never a prefix of an identifier (#468): a
321
+ // const member named `importPath` sits at line head exactly where the
322
+ // keyword would, and reading it as the keyword flagged a compiling file
323
+ // "an import states no path". Go lexes by maximal munch — letters, digits,
324
+ // and `_` continuing the word make it one identifier — so the lookahead
325
+ // excludes exactly those, and every legal follower (`(`, a quote, an
326
+ // alias, whitespace) still opens.
327
+ for (const m of source.matchAll(/(?:^|;)[ \t]*import(?![\p{L}\p{Nd}_])/gmu)) {
314
328
  let at = m.index + m[0].length;
315
329
  while (at < source.length && /\s/u.test(source[at])) at += 1;
316
330
  if (at >= source.length) {