@ts-graphviz/ast 3.0.7 → 3.0.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @ts-graphviz/ast
2
2
 
3
+ ## 3.0.8
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1763](https://github.com/ts-graphviz/ts-graphviz/pull/1763) [`24cc46e`](https://github.com/ts-graphviz/ts-graphviz/commit/24cc46ebf0078440fa498f89b21e7224725c16b6) Thanks [@kamiazya](https://github.com/kamiazya)! - Options are read from their own properties only, and values are always written as DOT text:
8
+
9
+ - `toStream` / `toFile`, `parse`, `stringify`, `fromModel` / `toModel`, `fromDot` and `toDot` read only the options their caller set. `toStream` / `toFile` also pass Node.js only the options and environment they set.
10
+ - Attribute keys and values, IDs, ports, compass points and comments are written so that DOT reads them back as the same text. A string of the form `<…>` is written as an HTML-like label only when it is well-formed; otherwise it is written as a quoted string.
11
+ - Writing attribute values and reading block comments are faster on large inputs.
12
+
3
13
  ## 3.0.7
4
14
 
5
15
  ### Patch Changes
package/README.md CHANGED
@@ -22,7 +22,7 @@ This package contains the module for processing the DOT language at the **A**bst
22
22
 
23
23
  [![GitHub](https://img.shields.io/badge/-GitHub-181717?logo=GitHub&style=flat)](https://github.com/ts-graphviz/ts-graphviz)
24
24
  [![npm](https://img.shields.io/badge/-npm-CB3837?logo=npm&style=flat)](https://www.npmjs.com/package/ts-graphviz)
25
- [![Reference](https://img.shields.io/badge/-API_Reference-3178C6?logo=TypeScript&style=flat&logoColor=fff)](https://ts-graphviz.github.io/ts-graphviz/)
25
+ [![Reference](https://img.shields.io/badge/-API_Reference-3178C6?logo=TypeScript&style=flat&logoColor=fff)](https://ts-graphviz.github.io/api/)
26
26
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/ts-graphviz/ts-graphviz)
27
27
 
28
28
  [![Sponsor](https://img.shields.io/badge/-GitHub%20Sponsor-fff?logo=GitHub%20Sponsors&style=flat)](https://github.com/sponsors/ts-graphviz)
@@ -175,6 +175,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
175
175
  <td align="center" valign="top" width="14.28%"><a href="http://www.walterra.dev"><img src="https://avatars.githubusercontent.com/u/230104?v=4?s=100" width="100px;" alt="Walter Rafelsberger"/><br /><sub><b>Walter Rafelsberger</b></sub></a><br /><a href="#question-walterra" title="Answering Questions">💬</a></td>
176
176
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/grsjst"><img src="https://avatars.githubusercontent.com/u/4739018?v=4?s=100" width="100px;" alt="grsjst"/><br /><sub><b>grsjst</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Agrsjst" title="Bug reports">🐛</a></td>
177
177
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/stephenirven"><img src="https://avatars.githubusercontent.com/u/4293560?v=4?s=100" width="100px;" alt="Steve"/><br /><sub><b>Steve</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Astephenirven" title="Bug reports">🐛</a></td>
178
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/btomaj"><img src="https://avatars.githubusercontent.com/u/2638986?v=4?s=100" width="100px;" alt="poetry by"/><br /><sub><b>poetry by</b></sub></a><br /><a href="https://github.com/ts-graphviz/ts-graphviz/issues?q=author%3Abtomaj" title="Bug reports">🐛</a></td>
178
179
  </tr>
179
180
  </tbody>
180
181
  </table>
package/lib/ast.js CHANGED
@@ -1,4 +1,15 @@
1
1
  import { isNodeModel, isForwardRefNode, createModelsContext } from "@ts-graphviz/common";
2
+ function ownOptions(options) {
3
+ const own = /* @__PURE__ */ Object.create(null);
4
+ if (options != null) {
5
+ const descriptors = Object.getOwnPropertyDescriptors(options);
6
+ for (const key of Object.getOwnPropertyNames(descriptors)) {
7
+ const descriptor = descriptors[key];
8
+ own[key] = "value" in descriptor ? descriptor.value : descriptor.get?.call(options);
9
+ }
10
+ }
11
+ return own;
12
+ }
2
13
  class ASTNodeCountExceededError extends Error {
3
14
  /**
4
15
  * Constructor
@@ -22,7 +33,8 @@ class Builder {
22
33
  */
23
34
  constructor(options) {
24
35
  this.options = options;
25
- this.maxNodes = options?.maxASTNodes ?? DEFAULT_MAX_AST_NODES;
36
+ this.options = ownOptions(options);
37
+ this.maxNodes = this.options.maxASTNodes ?? DEFAULT_MAX_AST_NODES;
26
38
  }
27
39
  nodeCount = 0;
28
40
  maxNodes;
@@ -465,7 +477,7 @@ function peg$parse(input, options) {
465
477
  "Comment",
466
478
  {
467
479
  kind: "Block",
468
- value: dedent(v.join("").replace(/[ \t]*\*/g, ""))
480
+ value: dedent(v.join("").replace(/^[ \t]*\*/gm, ""))
469
481
  },
470
482
  []
471
483
  );
@@ -2700,8 +2712,10 @@ function peg$parse(input, options) {
2700
2712
  const matches = str.match(/\n([\t ]+|(?!\s).)/g);
2701
2713
  if (matches) {
2702
2714
  const indentLengths = matches.map((match) => match.match(/[\t ]/g)?.length ?? 0);
2715
+ let indent = Infinity;
2716
+ for (const length of indentLengths) if (length < indent) indent = length;
2703
2717
  const pattern = new RegExp(`
2704
- [ ]{${Math.min(...indentLengths)}}`, "g");
2718
+ [ ]{${indent}}`, "g");
2705
2719
  return str.replace(pattern, "\n");
2706
2720
  }
2707
2721
  return str;
@@ -2763,7 +2777,7 @@ function parse(input, options) {
2763
2777
  maxEdgeChainDepth,
2764
2778
  maxInputSize,
2765
2779
  maxASTNodes
2766
- } = options ?? {};
2780
+ } = ownOptions(options);
2767
2781
  const inputSizeLimit = maxInputSize ?? DEFAULT_MAX_INPUT_SIZE;
2768
2782
  if (inputSizeLimit > 0) {
2769
2783
  const inputBytes = new TextEncoder().encode(input).length;
@@ -2830,13 +2844,13 @@ const AttributePrintPlugin = {
2830
2844
  };
2831
2845
  const ZERO_WIDTH_SPACE = "​";
2832
2846
  function escapeComment(value, kind) {
2833
- let escaped = value.replace(/\0/g, "");
2847
+ let escaped = String(value).replace(/\0/g, "");
2834
2848
  if (kind === "Block") {
2835
2849
  escaped = escaped.replace(/\*\//g, `*${ZERO_WIDTH_SPACE}/`);
2836
2850
  }
2837
2851
  return escaped;
2838
2852
  }
2839
- const EOL_PATTERN = /\r?\n/;
2853
+ const EOL_PATTERN = /\r\n|\r|\n/;
2840
2854
  const paddingMap = {
2841
2855
  Block: " * ",
2842
2856
  Macro: "# ",
@@ -2910,14 +2924,27 @@ const GraphPrintPlugin = {
2910
2924
  yield "}";
2911
2925
  }
2912
2926
  };
2913
- const escape = (value) => value.replace(/\0/g, "").replace(/(?<!\\)"|[\r\n]/g, escapeReplacer);
2914
- const escapeMap = {
2915
- "\r": String.raw`\r`,
2916
- "\n": String.raw`\n`,
2917
- '"': String.raw`\"`
2918
- };
2919
- function escapeReplacer(match) {
2920
- return escapeMap[match];
2927
+ function escape(value) {
2928
+ const text = String(value);
2929
+ let out = "";
2930
+ let backslashes = 0;
2931
+ for (const ch of text) {
2932
+ if (ch === "\0") continue;
2933
+ if (ch === "\\") {
2934
+ backslashes++;
2935
+ out += ch;
2936
+ continue;
2937
+ }
2938
+ if (ch === "\n" || ch === "\r") {
2939
+ if (backslashes % 2 === 1) out += "\\";
2940
+ out += ch === "\n" ? String.raw`\n` : String.raw`\r`;
2941
+ } else {
2942
+ if (ch === '"' && backslashes % 2 === 0) out += "\\";
2943
+ out += ch;
2944
+ }
2945
+ backslashes = 0;
2946
+ }
2947
+ return backslashes % 2 === 1 ? `${out}\\` : out;
2921
2948
  }
2922
2949
  const LiteralPrintPlugin = {
2923
2950
  match(ast) {
@@ -2971,11 +2998,11 @@ const NodeRefPrintPlugin = {
2971
2998
  },
2972
2999
  *print(context, ast) {
2973
3000
  yield* context.print(ast.id);
2974
- if (ast.port) {
3001
+ if (Object.hasOwn(ast, "port") && ast.port) {
2975
3002
  yield ":";
2976
3003
  yield* context.print(ast.port);
2977
3004
  }
2978
- if (ast.compass) {
3005
+ if (Object.hasOwn(ast, "compass") && ast.compass) {
2979
3006
  yield ":";
2980
3007
  yield* context.print(ast.compass);
2981
3008
  }
@@ -3017,6 +3044,7 @@ class Printer {
3017
3044
  */
3018
3045
  constructor(options = {}) {
3019
3046
  this.options = options;
3047
+ this.options = ownOptions(options);
3020
3048
  }
3021
3049
  /** @internal */
3022
3050
  #plugins = [...defaultPlugins$2];
@@ -3090,15 +3118,32 @@ function stringify(ast, options) {
3090
3118
  }
3091
3119
  return result;
3092
3120
  }
3121
+ function isBalancedMarkup(markup) {
3122
+ let depth = 0;
3123
+ for (const ch of markup) {
3124
+ if (ch === "<") depth++;
3125
+ else if (ch === ">" && --depth < 0) return false;
3126
+ }
3127
+ return depth === 0 && !markup.includes("\0");
3128
+ }
3129
+ function keyLiteral(createElement2, key) {
3130
+ const bare = typeof key === "string" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) && !/^(?:node|edge|graph|digraph|subgraph|strict)$/i.test(key);
3131
+ return createElement2(
3132
+ "Literal",
3133
+ { value: key, quoted: !bare },
3134
+ []
3135
+ );
3136
+ }
3137
+ const NUMERAL = /^-?(?:\.\d+|\d+(?:\.\d*)?)$/;
3093
3138
  function convertAttribute(createElement2, key, value) {
3094
3139
  if (typeof value === "string") {
3095
3140
  const trimmed = value.trim();
3096
- const isHTMLLike = /^<.+>$/ms.test(trimmed);
3141
+ const isHTMLLike = trimmed.length > 2 && trimmed.startsWith("<") && trimmed.endsWith(">") && isBalancedMarkup(trimmed.slice(1, -1));
3097
3142
  if (isHTMLLike) {
3098
3143
  return createElement2(
3099
3144
  "Attribute",
3100
3145
  {
3101
- key: createElement2("Literal", { value: key, quoted: false }, []),
3146
+ key: keyLiteral(createElement2, key),
3102
3147
  value: createElement2(
3103
3148
  "Literal",
3104
3149
  { value: trimmed.slice(1, -1), quoted: "html" },
@@ -3111,21 +3156,19 @@ function convertAttribute(createElement2, key, value) {
3111
3156
  return createElement2(
3112
3157
  "Attribute",
3113
3158
  {
3114
- key: createElement2("Literal", { value: key, quoted: false }, []),
3159
+ key: keyLiteral(createElement2, key),
3115
3160
  value: createElement2("Literal", { value, quoted: true }, [])
3116
3161
  },
3117
3162
  []
3118
3163
  );
3119
3164
  }
3165
+ const text = String(value);
3166
+ const bare = typeof value === "boolean" || typeof value === "number" && NUMERAL.test(text);
3120
3167
  return createElement2(
3121
3168
  "Attribute",
3122
3169
  {
3123
- key: createElement2("Literal", { value: key, quoted: false }, []),
3124
- value: createElement2(
3125
- "Literal",
3126
- { value: String(value), quoted: false },
3127
- []
3128
- )
3170
+ key: keyLiteral(createElement2, key),
3171
+ value: createElement2("Literal", { value: text, quoted: !bare }, [])
3129
3172
  },
3130
3173
  []
3131
3174
  );
@@ -3209,6 +3252,9 @@ const AttributeListPlugin = {
3209
3252
  );
3210
3253
  }
3211
3254
  };
3255
+ function ownField(target, field) {
3256
+ return Object.hasOwn(target, field) ? target[field] : void 0;
3257
+ }
3212
3258
  const EdgePlugin$1 = {
3213
3259
  match(model) {
3214
3260
  return model.$$type === "Edge";
@@ -3247,18 +3293,18 @@ const EdgePlugin$1 = {
3247
3293
  },
3248
3294
  []
3249
3295
  ),
3250
- port: target.port ? createElement2(
3296
+ port: ownField(target, "port") ? createElement2(
3251
3297
  "Literal",
3252
3298
  {
3253
- value: target.port,
3299
+ value: ownField(target, "port"),
3254
3300
  quoted: true
3255
3301
  },
3256
3302
  []
3257
3303
  ) : void 0,
3258
- compass: target.compass ? createElement2(
3304
+ compass: ownField(target, "compass") ? createElement2(
3259
3305
  "Literal",
3260
3306
  {
3261
- value: target.compass,
3307
+ value: ownField(target, "compass"),
3262
3308
  quoted: true
3263
3309
  },
3264
3310
  []
@@ -3298,18 +3344,18 @@ const EdgePlugin$1 = {
3298
3344
  },
3299
3345
  []
3300
3346
  ),
3301
- port: n.port ? createElement2(
3347
+ port: ownField(n, "port") ? createElement2(
3302
3348
  "Literal",
3303
3349
  {
3304
- value: n.port,
3350
+ value: ownField(n, "port"),
3305
3351
  quoted: true
3306
3352
  },
3307
3353
  []
3308
3354
  ) : void 0,
3309
- compass: n.compass ? createElement2(
3355
+ compass: ownField(n, "compass") ? createElement2(
3310
3356
  "Literal",
3311
3357
  {
3312
- value: n.compass,
3358
+ value: ownField(n, "compass"),
3313
3359
  quoted: true
3314
3360
  },
3315
3361
  []
@@ -3428,6 +3474,7 @@ const defaultPlugins$1 = [
3428
3474
  class FromModelConverter {
3429
3475
  constructor(options = {}) {
3430
3476
  this.options = options;
3477
+ this.options = ownOptions(options);
3431
3478
  }
3432
3479
  /** @hidden */
3433
3480
  #plugins = [...defaultPlugins$1];
@@ -3512,21 +3559,20 @@ function collectAttributes(attrs) {
3512
3559
  return acc;
3513
3560
  }, {});
3514
3561
  }
3562
+ function toEdgeTarget(t) {
3563
+ return {
3564
+ id: t.id.value,
3565
+ port: Object.hasOwn(t, "port") ? t.port?.value : void 0,
3566
+ compass: Object.hasOwn(t, "compass") ? t.compass?.value : void 0
3567
+ };
3568
+ }
3515
3569
  function convertToEdgeTargetTuple(edge) {
3516
3570
  return edge.targets.map((t) => {
3517
3571
  switch (t.type) {
3518
3572
  case "NodeRef":
3519
- return {
3520
- id: t.id.value,
3521
- port: t.port?.value,
3522
- compass: t.compass?.value
3523
- };
3573
+ return toEdgeTarget(t);
3524
3574
  case "NodeRefGroup":
3525
- return t.children.map((t2) => ({
3526
- id: t2.id.value,
3527
- port: t2.port?.value,
3528
- compass: t2.compass?.value
3529
- }));
3575
+ return t.children.map(toEdgeTarget);
3530
3576
  }
3531
3577
  });
3532
3578
  }
@@ -3647,6 +3693,7 @@ const defaultPlugins = [
3647
3693
  class ToModelConverter {
3648
3694
  constructor(options = {}) {
3649
3695
  this.options = options;
3696
+ this.options = ownOptions(options);
3650
3697
  }
3651
3698
  /** @hidden */
3652
3699
  plugins = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-graphviz/ast",
3
- "version": "3.0.7",
3
+ "version": "3.0.8",
4
4
  "description": "Graphviz AST(Abstract Syntax Tree) Utilities",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/ts-graphviz/ts-graphviz#readme",