as-test 1.1.2 → 1.1.4

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.
@@ -3,7 +3,7 @@ import { diff } from "typer-diff";
3
3
  import { readFileSync } from "fs";
4
4
  import * as path from "path";
5
5
  import { formatSpecDisplayPath, formatTime } from "../util.js";
6
- import { describeCoveragePoint, readCoverageSourceLine, resolveCoverageHighlightSpan, } from "../coverage-points.js";
6
+ import { describeCoveragePoint } from "../coverage-points.js";
7
7
  export const createReporter = (context) => {
8
8
  return new DefaultReporter(context);
9
9
  };
@@ -288,7 +288,7 @@ class DefaultReporter {
288
288
  if (event.coverageSummary.enabled) {
289
289
  renderCoverageSummary(event.coverageSummary, event.showCoverage);
290
290
  if (event.showCoverage && event.coverageSummary.uncovered) {
291
- renderCoveragePoints(event.coverageSummary.files);
291
+ renderCoveragePoints(event.coverageSummary.files, Boolean(event.verbose || event.showCoverageAll));
292
292
  }
293
293
  }
294
294
  renderTotals(event.stats, event);
@@ -831,7 +831,7 @@ function renderCoverageSummary(summary, showCoverage) {
831
831
  console.log(chalk.dim(` ... ${ranked.length - 8} more files`));
832
832
  }
833
833
  }
834
- function renderCoveragePoints(files) {
834
+ function renderCoveragePoints(files, expandNested) {
835
835
  console.log("");
836
836
  console.log(chalk.bold("Coverage Gaps"));
837
837
  const sortedFiles = [...files].sort((a, b) => a.file.localeCompare(b.file));
@@ -842,28 +842,124 @@ function renderCoveragePoints(files) {
842
842
  displayType: describeCoveragePoint(point.file, point.line, point.column, point.type).displayType,
843
843
  })));
844
844
  const layout = createCoverageGapLayout(missingPoints);
845
+ let renderedFileCount = 0;
846
+ let collapsedNestedPoints = 0;
845
847
  for (const file of sortedFiles) {
846
- const points = [...file.points].sort((a, b) => {
847
- if (a.line != b.line)
848
- return a.line - b.line;
849
- if (a.column != b.column)
850
- return a.column - b.column;
851
- return a.type.localeCompare(b.type);
852
- });
848
+ const points = [...file.points].sort(compareCoverageGapPoints);
853
849
  const missing = points.filter((point) => !point.executed);
854
850
  if (!missing.length)
855
851
  continue;
852
+ if (renderedFileCount > 0) {
853
+ console.log("");
854
+ }
856
855
  console.log(` ${chalk.bold(toRelativeResultPath(file.file))} ${chalk.dim(`(${missing.length} uncovered)`)}`);
856
+ const pointsByHash = new Map(points.map((point) => [point.hash, point]));
857
+ const childrenByParent = new Map();
858
+ const roots = [];
857
859
  for (const point of points) {
858
- if (point.executed)
859
- continue;
860
- const location = `${toRelativeResultPath(point.file)}:${point.line}:${point.column}`;
861
- const snippet = formatCoverageSnippet(point.file, point.line, point.column);
862
- const typeLabel = describeCoveragePoint(point.file, point.line, point.column, point.type).displayType.padEnd(layout.typeWidth + 4);
863
- const locationLabel = location.padEnd(layout.locationWidth + 4);
864
- console.log(` ${chalk.red("x")} ${chalk.dim(typeLabel)}${chalk.dim(locationLabel)}${snippet}`);
860
+ const parentHash = point.parentHash ?? "";
861
+ if (parentHash.length && pointsByHash.has(parentHash)) {
862
+ const children = childrenByParent.get(parentHash) ?? [];
863
+ children.push(point);
864
+ childrenByParent.set(parentHash, children);
865
+ }
866
+ else {
867
+ roots.push(point);
868
+ }
869
+ }
870
+ const visibleRoots = roots.filter((point) => shouldRenderCoveragePoint(point, childrenByParent));
871
+ for (let i = 0; i < visibleRoots.length; i++) {
872
+ collapsedNestedPoints += renderCoveragePointTree(visibleRoots[i], childrenByParent, layout, [], i == visibleRoots.length - 1, expandNested);
865
873
  }
874
+ renderedFileCount++;
866
875
  }
876
+ if (!expandNested && collapsedNestedPoints > 0) {
877
+ console.log("");
878
+ console.log(chalk.dim(" Run with --show-coverage=all or --verbose to expand nested coverage gaps."));
879
+ }
880
+ }
881
+ function renderCoveragePointTree(point, childrenByParent, layout, ancestorHasNext, isLast, expandNested) {
882
+ const visibleChildren = [...(childrenByParent.get(point.hash) ?? [])]
883
+ .filter((child) => shouldRenderCoveragePoint(child, childrenByParent))
884
+ .sort(compareCoverageGapPoints);
885
+ const nestedUncoveredCount = countNestedUncoveredPoints(visibleChildren, childrenByParent);
886
+ if (!point.executed) {
887
+ renderCoverageGapLine(point, layout, ancestorHasNext, isLast);
888
+ if (nestedUncoveredCount > 0) {
889
+ if (expandNested) {
890
+ let rendered = 0;
891
+ for (let i = 0; i < visibleChildren.length; i++) {
892
+ rendered += renderCoveragePointTree(visibleChildren[i], childrenByParent, layout, [...ancestorHasNext, !isLast], i == visibleChildren.length - 1, expandNested);
893
+ }
894
+ return 1 + rendered;
895
+ }
896
+ const treePrefix = buildCoverageTreePrefix([...ancestorHasNext, !isLast], true);
897
+ console.log(` ${treePrefix}${chalk.dim(`(+${nestedUncoveredCount} nested uncovered point${nestedUncoveredCount == 1 ? "" : "s"})`)}`);
898
+ return nestedUncoveredCount;
899
+ }
900
+ return 0;
901
+ }
902
+ if (nestedUncoveredCount <= 0)
903
+ return 0;
904
+ renderCoverageScopeHeader(point, layout, ancestorHasNext, isLast);
905
+ let rendered = 0;
906
+ for (let i = 0; i < visibleChildren.length; i++) {
907
+ rendered += renderCoveragePointTree(visibleChildren[i], childrenByParent, layout, [...ancestorHasNext, !isLast], i == visibleChildren.length - 1, expandNested);
908
+ }
909
+ return rendered;
910
+ }
911
+ function shouldRenderCoveragePoint(point, childrenByParent) {
912
+ if (!point.executed)
913
+ return true;
914
+ return (countNestedUncoveredPoints(childrenByParent.get(point.hash) ?? [], childrenByParent) > 0);
915
+ }
916
+ function countNestedUncoveredPoints(points, childrenByParent) {
917
+ let count = 0;
918
+ for (const point of points) {
919
+ if (!point.executed)
920
+ count++;
921
+ count += countNestedUncoveredPoints(childrenByParent.get(point.hash) ?? [], childrenByParent);
922
+ }
923
+ return count;
924
+ }
925
+ function renderCoverageGapLine(point, layout, ancestorHasNext, isLast) {
926
+ const location = `${toRelativeResultPath(point.file)}:${point.line}:${point.column}`;
927
+ const snippet = formatCoverageSnippet(point.file, point.line, point.column, point.type, ancestorHasNext.length);
928
+ const typeLabel = describeCoveragePoint(point.file, point.line, point.column, point.type).displayType.padEnd(layout.typeWidth + 6);
929
+ const locationLabel = location.padEnd(layout.locationWidth + 6);
930
+ const treePrefix = buildCoverageTreePrefix(ancestorHasNext, isLast);
931
+ const meta = `${typeLabel}${locationLabel}`;
932
+ console.log(` ${treePrefix}${chalk.dim(meta)} ${snippet}`);
933
+ }
934
+ function renderCoverageScopeHeader(point, layout, ancestorHasNext, isLast) {
935
+ const descriptor = describeCoveragePoint(point.file, point.line, point.column, point.type);
936
+ const label = point.scopeKind || descriptor.displayType;
937
+ const location = `${toRelativeResultPath(point.file)}:${point.line}:${point.column}`;
938
+ const locationLabel = location.padEnd(layout.locationWidth + 6);
939
+ const typeLabel = label.padEnd(layout.typeWidth + 6);
940
+ const snippet = formatCoverageSnippet(point.file, point.line, point.column, point.type, ancestorHasNext.length);
941
+ const treePrefix = buildCoverageTreePrefix(ancestorHasNext, isLast);
942
+ const meta = `${typeLabel}${locationLabel}`;
943
+ console.log(` ${treePrefix}${chalk.dim(meta)} ${chalk.dim(snippet)}`);
944
+ }
945
+ function buildCoverageTreePrefix(ancestorHasNext, isLast) {
946
+ let out = "";
947
+ for (const hasNext of ancestorHasNext) {
948
+ out += hasNext ? "│ " : " ";
949
+ }
950
+ out += isLast ? "└─" : "├─";
951
+ return chalk.dim(out);
952
+ }
953
+ function compareCoverageGapPoints(a, b) {
954
+ if (a.line != b.line)
955
+ return a.line - b.line;
956
+ if (a.column != b.column)
957
+ return a.column - b.column;
958
+ if ((a.depth ?? 0) != (b.depth ?? 0))
959
+ return (a.depth ?? 0) - (b.depth ?? 0);
960
+ if (a.type != b.type)
961
+ return a.type.localeCompare(b.type);
962
+ return a.hash.localeCompare(b.hash);
867
963
  }
868
964
  function renderCoverageBar(percent) {
869
965
  const slots = 12;
@@ -877,32 +973,25 @@ function createCoverageGapLayout(points) {
877
973
  .length), 1),
878
974
  };
879
975
  }
880
- function formatCoverageSnippet(file, line, column) {
881
- const sourceLine = readCoverageSourceLine(file, line);
882
- if (!sourceLine)
883
- return "";
884
- const expanded = sourceLine.replace(/\t/g, " ");
885
- const firstNonWhitespace = expanded.search(/\S/);
886
- if (firstNonWhitespace == -1)
887
- return "";
888
- const visible = expanded.slice(firstNonWhitespace).trimEnd();
976
+ function formatCoverageSnippet(file, line, column, fallbackType, _depth) {
977
+ const descriptor = describeCoveragePoint(file, line, column, fallbackType);
978
+ const visible = descriptor.visible;
889
979
  if (!visible.length)
890
980
  return "";
891
981
  const maxWidth = 72;
892
- const focus = Math.max(0, Math.min(visible.length - 1, Math.max(0, column - 1 - firstNonWhitespace)));
982
+ const focus = Math.max(0, Math.min(visible.length - 1, descriptor.focus));
893
983
  if (visible.length <= maxWidth) {
894
- return styleCoverageSnippetWindow(visible, 0, visible.length, focus);
984
+ return styleCoverageSnippetWindow(visible, 0, visible.length, focus, descriptor.highlightStart, descriptor.highlightEnd);
895
985
  }
896
986
  const start = Math.max(0, Math.min(visible.length - maxWidth, focus - Math.floor(maxWidth / 2)));
897
987
  const end = Math.min(visible.length, start + maxWidth);
898
- return styleCoverageSnippetWindow(visible, start, end, focus);
988
+ return styleCoverageSnippetWindow(visible, start, end, focus, descriptor.highlightStart, descriptor.highlightEnd);
899
989
  }
900
- function styleCoverageSnippetWindow(visible, start, end, focus) {
990
+ function styleCoverageSnippetWindow(visible, start, end, focus, highlightStart, highlightEnd) {
901
991
  const prefix = start > 0 ? "..." : "";
902
992
  const suffix = end < visible.length ? "..." : "";
903
993
  const slice = visible.slice(start, end);
904
994
  const localFocus = Math.max(0, Math.min(slice.length - 1, focus - start));
905
- const [highlightStart, highlightEnd] = resolveCoverageHighlightSpan(visible, focus);
906
995
  const localStart = Math.max(0, Math.min(slice.length, highlightStart - start));
907
996
  const localEnd = Math.max(localStart + 1, Math.min(slice.length, highlightEnd - start));
908
997
  if (!slice.length)
package/bin/types.js CHANGED
@@ -18,7 +18,9 @@ export class Config {
18
18
  export class CoverageOptions {
19
19
  constructor() {
20
20
  this.enabled = false;
21
+ this.mode = "project";
21
22
  this.includeSpecs = false;
23
+ this.dependencies = [];
22
24
  this.include = [];
23
25
  this.exclude = [];
24
26
  this.ignore = new CoverageIgnoreOptions();
package/bin/util.js CHANGED
@@ -332,7 +332,7 @@ function validateCoverageValue(value, path, issues) {
332
332
  issues.push({
333
333
  path,
334
334
  message: "must be a boolean or object",
335
- fix: 'use true/false or { "enabled": true, "includeSpecs": false, "include": ["assembly/**/*.ts"], "exclude": ["assembly/__tests__/**/*.spec.ts"] }',
335
+ fix: 'use true/false or { "enabled": true, "mode": "project", "includeSpecs": false, "dependencies": ["json-as"], "include": ["assembly/**/*.ts"], "exclude": ["assembly/__tests__/**/*.spec.ts"] }',
336
336
  });
337
337
  return;
338
338
  }
@@ -344,6 +344,22 @@ function validateCoverageValue(value, path, issues) {
344
344
  fix: "set to true or false",
345
345
  });
346
346
  }
347
+ if ("mode" in obj && obj.mode != undefined) {
348
+ if (typeof obj.mode != "string") {
349
+ issues.push({
350
+ path: `${path}.mode`,
351
+ message: 'must be "project" or "all"',
352
+ fix: 'set "mode" to "project" or "all"',
353
+ });
354
+ }
355
+ else if (obj.mode != "project" && obj.mode != "all") {
356
+ issues.push({
357
+ path: `${path}.mode`,
358
+ message: 'must be "project" or "all"',
359
+ fix: 'set "mode" to "project" or "all"',
360
+ });
361
+ }
362
+ }
347
363
  if ("includeSpecs" in obj && typeof obj.includeSpecs != "boolean") {
348
364
  issues.push({
349
365
  path: `${path}.includeSpecs`,
@@ -351,6 +367,7 @@ function validateCoverageValue(value, path, issues) {
351
367
  fix: "set to true or false",
352
368
  });
353
369
  }
370
+ validateStringArrayField(obj, "dependencies", path, issues);
354
371
  validateStringArrayField(obj, "include", path, issues);
355
372
  validateStringArrayField(obj, "exclude", path, issues);
356
373
  if ("ignore" in obj && obj.ignore != undefined) {
@@ -929,6 +946,8 @@ function cloneCoverageOptions(coverage) {
929
946
  if (typeof coverage == "boolean")
930
947
  return coverage;
931
948
  const cloned = Object.assign(new CoverageOptions(), coverage);
949
+ cloned.mode = coverage.mode ?? "project";
950
+ cloned.dependencies = [...(coverage.dependencies ?? [])];
932
951
  cloned.include = [...(coverage.include ?? [])];
933
952
  cloned.exclude = [...(coverage.exclude ?? [])];
934
953
  cloned.ignore = Object.assign(new CoverageIgnoreOptions(), coverage.ignore);
@@ -1035,8 +1054,12 @@ function mergeCoverageConfig(base, override, raw) {
1035
1054
  const rawObject = raw;
1036
1055
  if ("enabled" in rawObject)
1037
1056
  mergedBase.enabled = overrideOptions.enabled;
1057
+ if ("mode" in rawObject)
1058
+ mergedBase.mode = overrideOptions.mode;
1038
1059
  if ("includeSpecs" in rawObject)
1039
1060
  mergedBase.includeSpecs = overrideOptions.includeSpecs;
1061
+ if ("dependencies" in rawObject)
1062
+ mergedBase.dependencies = [...overrideOptions.dependencies];
1040
1063
  if ("include" in rawObject)
1041
1064
  mergedBase.include = [...overrideOptions.include];
1042
1065
  if ("exclude" in rawObject)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "as-test",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "author": "Jairus Tanaka",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,6 +20,7 @@
20
20
  "as-test": "./",
21
21
  "assemblyscript": "^0.28.17",
22
22
  "assemblyscript-prettier": "^3.0.4",
23
+ "json-as": "^1.3.5",
23
24
  "prettier": "3.8.3",
24
25
  "try-as": "^1.0.1",
25
26
  "typescript": "^6.0.3",
@@ -93,7 +94,7 @@
93
94
  "docs:preview": "vitepress preview docs",
94
95
  "format": "prettier -w .",
95
96
  "release:check": "npm run build:cli && npm run build:lib && npm run build:transform && npm run test && npm run test:integration && npm run test:examples && npm pack --dry-run --cache /tmp/as-test-npm-cache",
96
- "prepublishOnly": "npm run build:cli && npm run build:lib && npm run build:transform && npm run test && npm run test:integration"
97
+ "prepublishOnly": "npm run build:cli && npm run build:lib && npm run build:transform && npm run format && npm run test && npm run test:integration"
97
98
  },
98
99
  "type": "module"
99
100
  }