@cplieger/deadset-ts 7.1.0 → 7.2.0-dev.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.
Files changed (43) hide show
  1. package/dist/src/alias-chain.js +6 -2
  2. package/dist/src/analysis.js +3 -1
  3. package/dist/src/config.js +4 -0
  4. package/dist/src/conformance-record.js +2 -2
  5. package/dist/src/decorator.js +3 -2
  6. package/dist/src/findings/unused-declarations.js +3 -3
  7. package/dist/src/injection-container.js +28 -25
  8. package/dist/src/intra-function-parts.js +72 -11
  9. package/dist/src/inventory.js +18 -3
  10. package/dist/src/kinds.js +2 -2
  11. package/dist/src/ref.js +3 -2
  12. package/dist/src/references.js +38 -20
  13. package/dist/src/report.js +1 -1
  14. package/dist/src/resolve.js +37 -4
  15. package/dist/src/schema.js +4 -1
  16. package/dist/src/suppress-file.js +43 -9
  17. package/dist/src/suppress-inline.js +35 -12
  18. package/dist/src/suppress.js +3 -2
  19. package/dist/src/verbs/analyze.js +7 -3
  20. package/dist/src/version.js +1 -1
  21. package/jsr.json +1 -1
  22. package/package.json +1 -1
  23. package/src/alias-chain.ts +7 -2
  24. package/src/analysis.ts +3 -3
  25. package/src/config.ts +9 -1
  26. package/src/conformance-record.ts +2 -2
  27. package/src/decorator.ts +8 -2
  28. package/src/findings/unused-declarations.ts +3 -3
  29. package/src/injection-container.ts +31 -26
  30. package/src/intra-function-parts.ts +95 -11
  31. package/src/inventory.ts +26 -2
  32. package/src/kinds.ts +2 -2
  33. package/src/ref.ts +3 -2
  34. package/src/references.ts +55 -21
  35. package/src/report.ts +1 -1
  36. package/src/resolve.ts +56 -6
  37. package/src/schema.ts +10 -14
  38. package/src/signatures.ts +6 -1
  39. package/src/suppress-file.ts +48 -9
  40. package/src/suppress-inline.ts +57 -15
  41. package/src/suppress.ts +3 -2
  42. package/src/verbs/analyze.ts +7 -2
  43. package/src/version.ts +1 -1
package/src/schema.ts CHANGED
@@ -1,19 +1,12 @@
1
1
  import { ConfigError } from "./config.ts";
2
2
 
3
3
  /**
4
- * How one node of the closed key list is written.
5
- *
6
- * - `leaf` is a setting: a scalar, or an array of scalars.
7
- * - `section` is an object whose member names the key list declares and which
8
- * holds no value of its own.
9
- * - `map` is an object whose member names the key list leaves open, its values
10
- * scalars: the severity and provenance objects.
11
- * - `list` is an array whose entries are objects with declared members. Where
12
- * the key list admits an entry in more than one shape, the members are every
13
- * shape's together, and which of them one entry may hold is the decoder's
14
- * refusal, not the walk's.
15
- * - `object` is one setting written as an object: its member names are declared
16
- * and a source supplies the object whole.
4
+ * How one node of the closed key list is written: `leaf`, a scalar or an array of
5
+ * scalars; `section`, an object of declared members and no value of its own; `map`, an
6
+ * object of open member names and scalar values (severity, provenance); `list`, an
7
+ * array of objects holding every admitted shape's members; `object`, one setting
8
+ * written as an object of declared members and supplied whole. Which shape an entry or
9
+ * a value takes, where the key list admits several, is the decoder's refusal.
17
10
  */
18
11
  type KeyKind = "leaf" | "section" | "map" | "list" | "object";
19
12
 
@@ -46,7 +39,10 @@ export const SCHEMA_ROOT: KeyNode = {
46
39
  kind: "section",
47
40
  members: {
48
41
  contract_version: leaf,
49
- target: { kind: "section", members: { kind: leaf } },
42
+ target: {
43
+ kind: "section",
44
+ members: { kind: { kind: "object", members: { go: leaf, ts: leaf } } },
45
+ },
50
46
  analysis: {
51
47
  kind: "section",
52
48
  members: {
package/src/signatures.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  isPropertyDeclaration,
14
14
  isVariableDeclaration,
15
15
  type ArrowFunction,
16
+ type ConstructorDeclaration,
16
17
  type FunctionDeclaration,
17
18
  type FunctionExpression,
18
19
  type MethodDeclaration,
@@ -21,7 +22,11 @@ import {
21
22
 
22
23
  /** A function-like node that carries a body of its own. */
23
24
  export type SignatureNode =
24
- FunctionDeclaration | MethodDeclaration | ArrowFunction | FunctionExpression;
25
+ | FunctionDeclaration
26
+ | MethodDeclaration
27
+ | ArrowFunction
28
+ | FunctionExpression
29
+ | ConstructorDeclaration;
25
30
 
26
31
  /** Whether one node is a function value written in place: an arrow or a function expression. */
27
32
  function isFunctionValue(node: Node | undefined): node is ArrowFunction | FunctionExpression {
@@ -303,11 +303,12 @@ interface KeptRow {
303
303
  }
304
304
 
305
305
  /**
306
- * The rows of the other language's scheme that the baseline document `text` holds, in
307
- * document order and as written, which a write keeps. A document that is not a baseline,
308
- * or a row in a shape no record takes, is refused rather than dropped.
306
+ * The rows of a scheme other than `own` that the baseline document `text` holds, in
307
+ * document order and as written, which a write of `own`'s rows keeps. A document that is
308
+ * not a baseline, or a row in a shape no record takes, of any scheme, is refused rather
309
+ * than dropped.
309
310
  */
310
- export function otherSchemeRows(text: string): readonly KeptRow[] {
311
+ export function otherSchemeRows(text: string, own: string = OWN_SCHEME): readonly KeptRow[] {
311
312
  const refuse = (detail: string): never => {
312
313
  throw new Error(
313
314
  `${BASELINE_FILE} cannot be rewritten keeping the other language's rows: ${detail}`,
@@ -331,7 +332,7 @@ export function otherSchemeRows(text: string): readonly KeptRow[] {
331
332
  }
332
333
  // readRecord has checked every member is a string and the code and symbol are present.
333
334
  const { code, symbol, path, reason } = value as Readonly<Record<string, string>>;
334
- if (code === undefined || symbol === undefined || symbol.startsWith(OWN_SCHEME)) {
335
+ if (code === undefined || symbol === undefined || symbol.startsWith(own)) {
335
336
  return [];
336
337
  }
337
338
  return [
@@ -345,10 +346,37 @@ export function otherSchemeRows(text: string): readonly KeptRow[] {
345
346
  });
346
347
  }
347
348
 
349
+ /** The reference schemes in the order a baseline holds their rows. */
350
+ const SCHEME_ORDER: readonly string[] = ["go://", "ts://"];
351
+
352
+ function schemeRank(row: { readonly symbol: string }): number {
353
+ return SCHEME_ORDER.findIndex((scheme) => row.symbol.startsWith(scheme));
354
+ }
355
+
356
+ /** A surrogate half no other completes, which Go's encoding/json decodes as U+FFFD. */
357
+ const LONE_SURROGATE = /[\ud800-\udfff]/gu;
358
+
359
+ /** One string with every lone surrogate written as U+FFFD. */
360
+ function wellFormed(text: string): string {
361
+ return text.replace(LONE_SURROGATE, "\ufffd");
362
+ }
363
+
364
+ /**
365
+ * The pieces of a JSON document with U+2028 and U+2029 escaped, as Go's encoding/json
366
+ * writes them. Neither is JSON syntax, so each stands inside a string.
367
+ */
368
+ function* separatorsEscaped(pieces: Iterable<string>): Generator<string, void, undefined> {
369
+ for (const piece of pieces) {
370
+ yield piece.replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
371
+ }
372
+ }
373
+
348
374
  /**
349
- * A baseline holding one row per finding, in the order given, which is the report's, after
350
- * the other language's rows `kept`, so two analyzers writing one document in either order
351
- * write the same bytes. Every recorded row carries the provenance as its reason. A finding
375
+ * A baseline holding one row per finding, in the order given, which is the report's, and
376
+ * the other language's rows `kept`, Go's rows ahead of TypeScript's and each scheme's in
377
+ * the order given, so two analyzers writing one document in either order write the same
378
+ * bytes, in the form grammar/suppression.md fixes. Every recorded row carries the
379
+ * provenance as its reason. A finding
352
380
  * missing a value a row needs, and an identity missing its name or version, are refused
353
381
  * rather than written, because the grammar requires a reason on every row.
354
382
  */
@@ -371,5 +399,16 @@ export function writeBaseline(
371
399
  }
372
400
  return { code: found.code, symbol: found.symbol, path: found.path, reason };
373
401
  });
374
- return jsonChunks({ description: BASELINE_DESCRIPTION, baseline: [...kept, ...baseline] }, 2);
402
+ const rows = [...kept, ...baseline]
403
+ .sort((a, b) => schemeRank(a) - schemeRank(b))
404
+ .map((row: KeptRow): KeptRow => {
405
+ const { code, symbol, path, reason } = row;
406
+ return {
407
+ code: wellFormed(code),
408
+ symbol: wellFormed(symbol),
409
+ ...(path === undefined ? {} : { path: wellFormed(path) }),
410
+ ...(reason === undefined ? {} : { reason: wellFormed(reason) }),
411
+ };
412
+ });
413
+ return separatorsEscaped(jsonChunks({ description: BASELINE_DESCRIPTION, baseline: rows }, 2));
375
414
  }
@@ -4,12 +4,18 @@
4
4
  * below it, or on its own line where it trails a token there.
5
5
  */
6
6
 
7
- import { SyntaxKind, type Node, type SourceFile } from "@typescript/native/ast";
8
- import { nodeKey, type Inventory } from "./inventory.ts";
7
+ import {
8
+ isConstructorDeclaration,
9
+ SyntaxKind,
10
+ type Node,
11
+ type SourceFile,
12
+ } from "@typescript/native/ast";
13
+ import { isParameterProperty, nodeKey, type Inventory } from "./inventory.ts";
9
14
  import { byPosition, positionKey, renderPosition, type Position } from "./position.ts";
10
15
  import type { ProjectView } from "./session.ts";
11
16
  import { signatureOf } from "./signatures.ts";
12
17
  import {
18
+ isPartKind,
13
19
  SUPPRESSION_WITHOUT_REASON,
14
20
  SuppressionError,
15
21
  siteText,
@@ -166,35 +172,64 @@ interface Declared {
166
172
  readonly position: Position;
167
173
  }
168
174
 
175
+ /**
176
+ * What one placement binds: the declaration a record for a code that reports a
177
+ * declaration binds, and the one a record for a part kind binds, which holds the part.
178
+ * A parameter property's field is the first and its class the second; a constructor's
179
+ * plain parameter binds only the second, because a constructor is no declaration.
180
+ */
181
+ interface Binding {
182
+ readonly declared: Declared | undefined;
183
+ readonly part: Declared;
184
+ }
185
+
186
+ function bindingKey(one: Binding): string {
187
+ return `${one.declared?.id ?? ""}\u0000${one.part.id}`;
188
+ }
189
+
169
190
  /**
170
191
  * The declarations of one file by the line each begins on, the line of its first token: a
171
192
  * decorator, a modifier or the keyword. A variable's first token is its statement's
172
193
  * keyword, and a variable written on a later line of the statement also begins there. A
173
- * function also begins, for a directive, on the line of each of its parameters.
194
+ * function also begins, for a directive, on the line of each of its parameters, and a
195
+ * class, for a part kind, on the lines of its constructor and its constructor's parameters.
174
196
  */
175
197
  function declarationLines(
176
198
  file: SourceFile,
177
199
  held: Inventory,
178
200
  symbols: ReadonlyMap<string, Declared>,
179
- ): ReadonlyMap<number, readonly Declared[]> {
180
- const below = new Map<number, Declared[]>();
181
- const add = (offset: number, one: Declared): void => {
201
+ ): ReadonlyMap<number, readonly Binding[]> {
202
+ const below = new Map<number, Binding[]>();
203
+ const add = (offset: number, one: Binding): void => {
182
204
  const line = file.getLineAndCharacterOfPosition(tokenStart(file.text, offset)).line + 1;
183
205
  const at = below.get(line) ?? [];
184
- if (!at.some((known) => known.id === one.id)) {
206
+ if (!at.some((known) => bindingKey(known) === bindingKey(one))) {
185
207
  at.push(one);
186
208
  }
187
209
  below.set(line, at);
188
210
  };
189
- const visit = (node: Node): void => {
211
+ const declaredAt = (node: Node): Declared | undefined => {
190
212
  const id = held.declarations.get(nodeKey(file, node));
191
- const one = id === undefined ? undefined : symbols.get(id);
213
+ return id === undefined ? undefined : symbols.get(id);
214
+ };
215
+ const visit = (node: Node): void => {
216
+ const one = declaredAt(node);
217
+ const owner = isConstructorDeclaration(node) ? declaredAt(node.parent) : undefined;
218
+ if (owner !== undefined && isConstructorDeclaration(node)) {
219
+ for (const at of [node, ...node.parameters.filter((one) => !isParameterProperty(one))]) {
220
+ add(at.pos, { declared: undefined, part: owner });
221
+ }
222
+ }
223
+ const holder =
224
+ one !== undefined && isParameterProperty(node) ? declaredAt(node.parent.parent) : one;
225
+ if (one !== undefined && holder !== undefined) {
226
+ add(node.pos, { declared: one, part: holder });
227
+ }
192
228
  if (one !== undefined) {
193
- add(node.pos, one);
194
229
  // A parameter on a later line of a wrapped signature takes its directive on the
195
230
  // line above it, inside the parameter list, and the directive binds the function.
196
231
  for (const parameter of signatureOf(node)?.parameters ?? []) {
197
- add(parameter.pos, one);
232
+ add(parameter.pos, { declared: one, part: one });
198
233
  }
199
234
  const list = node.parent;
200
235
  if (
@@ -202,7 +237,7 @@ function declarationLines(
202
237
  list.kind === SyntaxKind.VariableDeclarationList &&
203
238
  list.parent.kind === SyntaxKind.VariableStatement
204
239
  ) {
205
- add(list.parent.pos, one);
240
+ add(list.parent.pos, { declared: one, part: one });
206
241
  }
207
242
  }
208
243
  node.forEachChild(visit);
@@ -225,7 +260,7 @@ export interface InlineDirective {
225
260
  readonly site: Position;
226
261
  readonly text: string;
227
262
  readonly directive: Directive;
228
- readonly bound: Declared[];
263
+ readonly bound: Binding[];
229
264
  }
230
265
 
231
266
  /**
@@ -282,7 +317,7 @@ export function inlineSuppressions(
282
317
  continue;
283
318
  }
284
319
  for (const one of written.bound) {
285
- if (!known.bound.some((other) => other.id === one.id)) {
320
+ if (!known.bound.some((other) => bindingKey(other) === bindingKey(one))) {
286
321
  known.bound.push(one);
287
322
  }
288
323
  }
@@ -311,8 +346,15 @@ export function inlineSuppressions(
311
346
  }
312
347
  break;
313
348
  case "directive": {
314
- const declared = [...bound].sort((a, b) => byPosition(a.position, b.position));
315
349
  for (const code of directive.codes) {
350
+ const targets = new Map<string, Declared>();
351
+ for (const one of bound) {
352
+ const target = isPartKind(code) ? one.part : one.declared;
353
+ if (target !== undefined) {
354
+ targets.set(target.id, target);
355
+ }
356
+ }
357
+ const declared = [...targets.values()].sort((a, b) => byPosition(a.position, b.position));
316
358
  const base = {
317
359
  code,
318
360
  path: site.path,
package/src/suppress.ts CHANGED
@@ -88,9 +88,10 @@ export const CODE_FORM = /^DS[0-9]{4}$/u;
88
88
 
89
89
  /**
90
90
  * A path inside the target: relative to the target root, the solidus as separator, no
91
- * leading `./`, no trailing solidus, no `.` or `..` segment, never absolute.
91
+ * leading `./`, no trailing solidus, no `.` or `..` segment, never absolute, and no
92
+ * control character; a backslash is a character of a name.
92
93
  */
93
- export const PATH_EXPRESSION = String.raw`^(?:[^/\\.\r\n][^/\\\r\n]*|\.[^/\\.\r\n][^/\\\r\n]*|\.\.[^/\\\r\n]+)(?:/(?:[^/\\.\r\n][^/\\\r\n]*|\.[^/\\.\r\n][^/\\\r\n]*|\.\.[^/\\\r\n]+))*$`;
94
+ export const PATH_EXPRESSION = String.raw`^(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.\.[^/\x00-\x1f\x7f]+)(?:/(?:[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.[^/.\x00-\x1f\x7f][^/\x00-\x1f\x7f]*|\.\.[^/\x00-\x1f\x7f]+))*$`;
94
95
 
95
96
  const PATH_FORM = new RegExp(PATH_EXPRESSION, "u");
96
97
 
@@ -6,7 +6,7 @@ import {
6
6
  type RunAnalysis,
7
7
  type RunProject,
8
8
  } from "../analysis.ts";
9
- import { ConfigError, type Format } from "../config.ts";
9
+ import { ConfigError, kindOf, type Format } from "../config.ts";
10
10
  import { CONFORMANCE, DECLARED_GAPS } from "../conformance.ts";
11
11
  import { moduleIdentity } from "../consumers.ts";
12
12
  import { analyzerProvenance, verdictOf } from "../findings-pass.ts";
@@ -16,6 +16,7 @@ import type { NotBuilt } from "../discover.ts";
16
16
  import { partialNotes } from "../partial.ts";
17
17
  import {
18
18
  buildReport,
19
+ LANGUAGE,
19
20
  capFindings,
20
21
  sortFindings,
21
22
  type Report,
@@ -265,7 +266,11 @@ export const analyzeVerb: Verb = ({ err, host, inputs, scope, openClient, option
265
266
  version,
266
267
  conformance: CONFORMANCE,
267
268
  declaredGaps: DECLARED_GAPS,
268
- target: { kind, root, identity: moduleIdentity(host, scoped.target) },
269
+ target: {
270
+ kind: kindOf(kind, LANGUAGE),
271
+ root,
272
+ identity: moduleIdentity(host, scoped.target),
273
+ },
269
274
  configurations: configurationsOf(run.projects, targetRoot),
270
275
  notBuilt: notBuiltOf(run.notBuilt, targetRoot),
271
276
  loaded: run.consumers.map((consumer) => ({
package/src/version.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * independently of the analyzer's own version, which is the one the host answers
5
5
  * from the package the analyzer was installed from.
6
6
  */
7
- export const CONTRACT_VERSION = "7.1.0";
7
+ export const CONTRACT_VERSION = "7.2.0";