comment-parser 1.0.0-beta.2 → 1.1.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 (99) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +30 -21
  3. package/browser/index.js +301 -261
  4. package/es6/index.d.ts +21 -4
  5. package/es6/index.js +21 -5
  6. package/es6/parser/block-parser.d.ts +18 -2
  7. package/es6/parser/block-parser.js +12 -12
  8. package/es6/parser/index.d.ts +2 -3
  9. package/es6/parser/index.js +25 -26
  10. package/es6/parser/source-parser.js +13 -15
  11. package/es6/parser/spec-parser.d.ts +1 -6
  12. package/es6/parser/spec-parser.js +4 -151
  13. package/es6/parser/tokenizers/description.d.ts +19 -0
  14. package/es6/parser/tokenizers/description.js +46 -0
  15. package/es6/parser/tokenizers/index.d.ts +7 -0
  16. package/es6/parser/tokenizers/index.js +1 -0
  17. package/es6/parser/tokenizers/name.d.ts +6 -0
  18. package/es6/parser/tokenizers/name.js +91 -0
  19. package/es6/parser/tokenizers/tag.d.ts +6 -0
  20. package/es6/parser/tokenizers/tag.js +24 -0
  21. package/es6/parser/tokenizers/type.d.ts +27 -0
  22. package/es6/parser/tokenizers/type.js +67 -0
  23. package/es6/stringifier/index.js +1 -6
  24. package/es6/stringifier/inspect.d.ts +2 -0
  25. package/es6/stringifier/inspect.js +41 -0
  26. package/es6/transforms/align.js +18 -32
  27. package/es6/transforms/indent.js +11 -22
  28. package/es6/transforms/index.d.ts +0 -2
  29. package/es6/transforms/index.js +2 -10
  30. package/es6/util.d.ts +11 -0
  31. package/es6/util.js +25 -25
  32. package/jest.config.js +12 -11
  33. package/lib/index.d.ts +21 -4
  34. package/lib/index.js +24 -6
  35. package/lib/parser/block-parser.d.ts +18 -2
  36. package/lib/parser/block-parser.js +12 -12
  37. package/lib/parser/index.d.ts +2 -3
  38. package/lib/parser/index.js +26 -27
  39. package/lib/parser/source-parser.js +14 -16
  40. package/lib/parser/spec-parser.d.ts +1 -6
  41. package/lib/parser/spec-parser.js +4 -156
  42. package/lib/parser/tokenizers/description.d.ts +19 -0
  43. package/lib/parser/tokenizers/description.js +51 -0
  44. package/lib/parser/tokenizers/index.d.ts +7 -0
  45. package/lib/parser/tokenizers/index.js +2 -0
  46. package/lib/parser/tokenizers/name.d.ts +6 -0
  47. package/lib/parser/tokenizers/name.js +94 -0
  48. package/lib/parser/tokenizers/tag.d.ts +6 -0
  49. package/lib/parser/tokenizers/tag.js +27 -0
  50. package/lib/parser/tokenizers/type.d.ts +27 -0
  51. package/lib/parser/tokenizers/type.js +70 -0
  52. package/lib/stringifier/index.js +1 -6
  53. package/lib/stringifier/inspect.d.ts +2 -0
  54. package/lib/stringifier/inspect.js +44 -0
  55. package/lib/transforms/align.js +20 -34
  56. package/lib/transforms/indent.js +12 -23
  57. package/lib/transforms/index.d.ts +0 -2
  58. package/lib/transforms/index.js +3 -13
  59. package/lib/util.d.ts +11 -0
  60. package/lib/util.js +27 -26
  61. package/migrate-1.0.md +7 -7
  62. package/package.json +1 -1
  63. package/src/index.ts +20 -2
  64. package/src/parser/block-parser.ts +19 -1
  65. package/src/parser/index.ts +19 -20
  66. package/src/parser/source-parser.ts +2 -2
  67. package/src/parser/spec-parser.ts +2 -170
  68. package/src/parser/tokenizers/description.ts +75 -0
  69. package/src/parser/tokenizers/index.ts +8 -0
  70. package/src/parser/tokenizers/name.ts +112 -0
  71. package/src/parser/tokenizers/tag.ts +30 -0
  72. package/src/parser/tokenizers/type.ts +92 -0
  73. package/src/stringifier/inspect.ts +64 -0
  74. package/src/transforms/index.ts +0 -3
  75. package/src/util.ts +20 -0
  76. package/tests/e2e/examples.js +39 -2
  77. package/tests/e2e/examples.spec.js +4 -2
  78. package/tests/e2e/issue-109.spec.js +49 -0
  79. package/tests/e2e/issue-112.spec.js +20 -0
  80. package/tests/e2e/issue-113.spec.js +23 -0
  81. package/tests/e2e/transforms.spec.js +5 -2
  82. package/tests/unit/inspect.spec.ts +36 -0
  83. package/tests/unit/{spacer.spec.ts → spacer-description-joiner.spec.ts} +6 -6
  84. package/tests/unit/spec-description-tokenizer.spec.ts +100 -7
  85. package/tests/unit/spec-name-tokenizer.spec.ts +113 -1
  86. package/tests/unit/spec-parser.spec.ts +6 -9
  87. package/tests/unit/spec-tag-tokenizer.spec.ts +1 -1
  88. package/tests/unit/spec-type-tokenizer.spec.ts +135 -1
  89. package/tests/unit/stringifier.spec.ts +0 -1
  90. package/tests/unit/transforms-align.spec.ts +1 -1
  91. package/tests/unit/util-rewire.spec.ts +107 -0
  92. package/tests/unit/util.spec.ts +0 -48
  93. package/tsconfig.es6.json +1 -1
  94. package/tsconfig.node.json +1 -1
  95. package/es6/parser/spacer.d.ts +0 -3
  96. package/es6/parser/spacer.js +0 -37
  97. package/lib/parser/spacer.d.ts +0 -3
  98. package/lib/parser/spacer.js +0 -40
  99. package/src/parser/spacer.ts +0 -45
@@ -1,11 +1,9 @@
1
- import { splitSpace, isSpace, seedSpec } from '../util';
2
1
  import { Line, Spec } from '../primitives';
3
- import { Spacer } from './spacer';
2
+ import { seedSpec } from '../util';
3
+ import { Tokenizer } from './tokenizers/index';
4
4
 
5
5
  export type Parser = (source: Line[]) => Spec;
6
6
 
7
- export type Tokenizer = (spec: Spec) => Spec;
8
-
9
7
  export interface Options {
10
8
  tokenizers: Tokenizer[];
11
9
  }
@@ -20,169 +18,3 @@ export default function getParser({ tokenizers }: Options): Parser {
20
18
  return spec;
21
19
  };
22
20
  }
23
-
24
- export function tagTokenizer(): Tokenizer {
25
- return (spec: Spec): Spec => {
26
- const { tokens } = spec.source[0];
27
- const match = tokens.description.match(/\s*(@(\S+))(\s*)/);
28
-
29
- if (match === null) {
30
- spec.problems.push({
31
- code: 'spec:tag:prefix',
32
- message: 'tag should start with "@" symbol',
33
- line: spec.source[0].number,
34
- critical: true,
35
- });
36
- return spec;
37
- }
38
-
39
- tokens.tag = match[1];
40
- tokens.postTag = match[3];
41
- tokens.description = tokens.description.slice(match[0].length);
42
-
43
- spec.tag = match[2];
44
- return spec;
45
- };
46
- }
47
-
48
- export function typeTokenizer(): Tokenizer {
49
- return (spec: Spec): Spec => {
50
- let res = '';
51
- let curlies = 0;
52
- const { tokens } = spec.source[0];
53
- const source = tokens.description.trimLeft();
54
-
55
- if (source[0] !== '{') return spec;
56
-
57
- for (const ch of source) {
58
- if (ch === '{') curlies++;
59
- if (ch === '}') curlies--;
60
- res += ch;
61
- if (curlies === 0) {
62
- break;
63
- }
64
- }
65
-
66
- if (curlies !== 0) {
67
- spec.problems.push({
68
- code: 'spec:type:unpaired-curlies',
69
- message: 'unpaired curlies',
70
- line: spec.source[0].number,
71
- critical: true,
72
- });
73
- return spec;
74
- }
75
-
76
- spec.type = res.slice(1, -1);
77
- tokens.type = res;
78
- [tokens.postType, tokens.description] = splitSpace(
79
- source.slice(tokens.type.length)
80
- );
81
-
82
- return spec;
83
- };
84
- }
85
-
86
- export function nameTokenizer(): Tokenizer {
87
- return (spec: Spec): Spec => {
88
- const { tokens } = spec.source[0];
89
- const source = tokens.description.trimLeft();
90
-
91
- const quotedGroups = source.split('"');
92
-
93
- // if it starts with quoted group, assume it is a literal
94
- if (
95
- quotedGroups.length > 1 &&
96
- quotedGroups[0] === '' &&
97
- quotedGroups.length % 2 === 1
98
- ) {
99
- spec.name = quotedGroups[1];
100
- tokens.name = `"${quotedGroups[1]}"`;
101
- [tokens.postName, tokens.description] = splitSpace(
102
- source.slice(tokens.name.length)
103
- );
104
- return spec;
105
- }
106
-
107
- let brackets = 0;
108
- let name = '';
109
- let optional = false;
110
- let defaultValue;
111
-
112
- // assume name is non-space string or anything wrapped into brackets
113
- for (const ch of source) {
114
- if (brackets === 0 && isSpace(ch)) break;
115
- if (ch === '[') brackets++;
116
- if (ch === ']') brackets--;
117
- name += ch;
118
- }
119
-
120
- if (brackets !== 0) {
121
- spec.problems.push({
122
- code: 'spec:name:unpaired-brackets',
123
- message: 'unpaired brackets',
124
- line: spec.source[0].number,
125
- critical: true,
126
- });
127
- return spec;
128
- }
129
-
130
- const nameToken = name;
131
-
132
- if (name[0] === '[' && name[name.length - 1] === ']') {
133
- optional = true;
134
- name = name.slice(1, -1);
135
-
136
- const parts = name.split('=');
137
- name = parts[0].trim();
138
- defaultValue = parts[1]?.trim();
139
-
140
- if (name === '') {
141
- spec.problems.push({
142
- code: 'spec:name:empty-name',
143
- message: 'empty name',
144
- line: spec.source[0].number,
145
- critical: true,
146
- });
147
- return spec;
148
- }
149
-
150
- if (parts.length > 2) {
151
- spec.problems.push({
152
- code: 'spec:name:invalid-default',
153
- message: 'invalid default value syntax',
154
- line: spec.source[0].number,
155
- critical: true,
156
- });
157
- return spec;
158
- }
159
-
160
- if (defaultValue === '') {
161
- spec.problems.push({
162
- code: 'spec:name:empty-default',
163
- message: 'empty default value',
164
- line: spec.source[0].number,
165
- critical: true,
166
- });
167
- return spec;
168
- }
169
- }
170
-
171
- spec.optional = optional;
172
- spec.name = name;
173
- tokens.name = nameToken;
174
-
175
- if (defaultValue !== undefined) spec.default = defaultValue;
176
- [tokens.postName, tokens.description] = splitSpace(
177
- source.slice(tokens.name.length)
178
- );
179
- return spec;
180
- };
181
- }
182
-
183
- export function descriptionTokenizer(join: Spacer): Tokenizer {
184
- return (spec: Spec): Spec => {
185
- spec.description = join(spec.source);
186
- return spec;
187
- };
188
- }
@@ -0,0 +1,75 @@
1
+ import { Spec, Line, Markers } from '../../primitives';
2
+ import { Tokenizer } from './index';
3
+
4
+ /**
5
+ * Walks over provided lines joining description token into a single string.
6
+ * */
7
+ export type Joiner = (lines: Line[]) => string;
8
+
9
+ /**
10
+ * Shortcut for standard Joiners
11
+ * compact - strip surrounding whitespace and concat lines using a single string
12
+ * preserve - preserves original whitespace and line breaks as is
13
+ */
14
+ export type Spacing = 'compact' | 'preserve' | Joiner;
15
+
16
+ /**
17
+ * Makes no changes to `spec.lines[].tokens` but joins them into `spec.description`
18
+ * following given spacing srtategy
19
+ * @param {Spacing} spacing tells how to handle the whitespace
20
+ */
21
+ export default function descriptionTokenizer(
22
+ spacing: Spacing = 'compact'
23
+ ): Tokenizer {
24
+ const join = getJoiner(spacing);
25
+ return (spec: Spec): Spec => {
26
+ spec.description = join(spec.source);
27
+ return spec;
28
+ };
29
+ }
30
+
31
+ export function getJoiner(spacing: Spacing): Joiner {
32
+ if (spacing === 'compact') return compactJoiner;
33
+ if (spacing === 'preserve') return preserveJoiner;
34
+ return spacing;
35
+ }
36
+
37
+ function compactJoiner(lines: Line[]): string {
38
+ return lines
39
+ .map(({ tokens: { description } }: Line) => description.trim())
40
+ .filter((description) => description !== '')
41
+ .join(' ');
42
+ }
43
+
44
+ const lineNo = (num: number, { tokens }: Line, i: number) =>
45
+ tokens.type === '' ? num : i;
46
+
47
+ const getDescription = ({ tokens }: Line) =>
48
+ (tokens.delimiter === '' ? tokens.start : tokens.postDelimiter.slice(1)) +
49
+ tokens.description;
50
+
51
+ function preserveJoiner(lines: Line[]): string {
52
+ if (lines.length === 0) return '';
53
+
54
+ // skip the opening line with no description
55
+ if (
56
+ lines[0].tokens.description === '' &&
57
+ lines[0].tokens.delimiter === Markers.start
58
+ )
59
+ lines = lines.slice(1);
60
+
61
+ // skip the closing line with no description
62
+ const lastLine = lines[lines.length - 1];
63
+
64
+ if (
65
+ lastLine !== undefined &&
66
+ lastLine.tokens.description === '' &&
67
+ lastLine.tokens.end.endsWith(Markers.end)
68
+ )
69
+ lines = lines.slice(0, -1);
70
+
71
+ // description starts at the last line of type definition
72
+ lines = lines.slice(lines.reduce(lineNo, 0));
73
+
74
+ return lines.map(getDescription).join('\n');
75
+ }
@@ -0,0 +1,8 @@
1
+ import { Spec } from '../../primitives';
2
+
3
+ /**
4
+ * Splits `spect.lines[].token.description` into other tokens,
5
+ * and populates the spec.{tag, name, type, description}. Invoked in a chaing
6
+ * with other tokens, operations listed above can be moved to separate tokenizers
7
+ */
8
+ export type Tokenizer = (spec: Spec) => Spec;
@@ -0,0 +1,112 @@
1
+ import { Spec, Line } from '../../primitives';
2
+ import { splitSpace, isSpace, seedBlock } from '../../util';
3
+ import { Tokenizer } from './index';
4
+
5
+ const isQuoted = (s: string) => s && s.startsWith('"') && s.endsWith('"');
6
+
7
+ /**
8
+ * Splits remaining `spec.lines[].tokens.description` into `name` and `descriptions` tokens,
9
+ * and populates the `spec.name`
10
+ */
11
+ export default function nameTokenizer(): Tokenizer {
12
+ const typeEnd = (num: number, { tokens }: Line, i: number) =>
13
+ tokens.type === '' ? num : i;
14
+
15
+ return (spec: Spec): Spec => {
16
+ // look for the name in the line where {type} ends
17
+ const { tokens } = spec.source[spec.source.reduce(typeEnd, 0)];
18
+ const source = tokens.description.trimLeft();
19
+
20
+ const quotedGroups = source.split('"');
21
+
22
+ // if it starts with quoted group, assume it is a literal
23
+ if (
24
+ quotedGroups.length > 1 &&
25
+ quotedGroups[0] === '' &&
26
+ quotedGroups.length % 2 === 1
27
+ ) {
28
+ spec.name = quotedGroups[1];
29
+ tokens.name = `"${quotedGroups[1]}"`;
30
+ [tokens.postName, tokens.description] = splitSpace(
31
+ source.slice(tokens.name.length)
32
+ );
33
+ return spec;
34
+ }
35
+
36
+ let brackets = 0;
37
+ let name = '';
38
+ let optional = false;
39
+ let defaultValue: string;
40
+
41
+ // assume name is non-space string or anything wrapped into brackets
42
+ for (const ch of source) {
43
+ if (brackets === 0 && isSpace(ch)) break;
44
+ if (ch === '[') brackets++;
45
+ if (ch === ']') brackets--;
46
+ name += ch;
47
+ }
48
+
49
+ if (brackets !== 0) {
50
+ spec.problems.push({
51
+ code: 'spec:name:unpaired-brackets',
52
+ message: 'unpaired brackets',
53
+ line: spec.source[0].number,
54
+ critical: true,
55
+ });
56
+ return spec;
57
+ }
58
+
59
+ const nameToken = name;
60
+
61
+ if (name[0] === '[' && name[name.length - 1] === ']') {
62
+ optional = true;
63
+ name = name.slice(1, -1);
64
+
65
+ const parts = name.split('=');
66
+ name = parts[0].trim();
67
+ if (parts[1] !== undefined)
68
+ defaultValue = parts.slice(1).join('=').trim();
69
+
70
+ if (name === '') {
71
+ spec.problems.push({
72
+ code: 'spec:name:empty-name',
73
+ message: 'empty name',
74
+ line: spec.source[0].number,
75
+ critical: true,
76
+ });
77
+ return spec;
78
+ }
79
+
80
+ if (defaultValue === '') {
81
+ spec.problems.push({
82
+ code: 'spec:name:empty-default',
83
+ message: 'empty default value',
84
+ line: spec.source[0].number,
85
+ critical: true,
86
+ });
87
+ return spec;
88
+ }
89
+
90
+ // has "=" and is not a string, except for "=>"
91
+ if (!isQuoted(defaultValue) && /=(?!>)/.test(defaultValue)) {
92
+ spec.problems.push({
93
+ code: 'spec:name:invalid-default',
94
+ message: 'invalid default value syntax',
95
+ line: spec.source[0].number,
96
+ critical: true,
97
+ });
98
+ return spec;
99
+ }
100
+ }
101
+
102
+ spec.optional = optional;
103
+ spec.name = name;
104
+ tokens.name = nameToken;
105
+
106
+ if (defaultValue !== undefined) spec.default = defaultValue;
107
+ [tokens.postName, tokens.description] = splitSpace(
108
+ source.slice(tokens.name.length)
109
+ );
110
+ return spec;
111
+ };
112
+ }
@@ -0,0 +1,30 @@
1
+ import { Spec } from '../../primitives';
2
+ import { Tokenizer } from './index';
3
+
4
+ /**
5
+ * Splits the `@prefix` from remaining `Spec.lines[].token.descrioption` into the `tag` token,
6
+ * and populates `spec.tag`
7
+ */
8
+ export default function tagTokenizer(): Tokenizer {
9
+ return (spec: Spec): Spec => {
10
+ const { tokens } = spec.source[0];
11
+ const match = tokens.description.match(/\s*(@(\S+))(\s*)/);
12
+
13
+ if (match === null) {
14
+ spec.problems.push({
15
+ code: 'spec:tag:prefix',
16
+ message: 'tag should start with "@" symbol',
17
+ line: spec.source[0].number,
18
+ critical: true,
19
+ });
20
+ return spec;
21
+ }
22
+
23
+ tokens.tag = match[1];
24
+ tokens.postTag = match[3];
25
+ tokens.description = tokens.description.slice(match[0].length);
26
+
27
+ spec.tag = match[2];
28
+ return spec;
29
+ };
30
+ }
@@ -0,0 +1,92 @@
1
+ import { Spec, Line, Tokens } from '../../primitives';
2
+ import { splitSpace } from '../../util';
3
+ import { Tokenizer } from './index';
4
+
5
+ /**
6
+ * Joiner is a function taking collected type token string parts,
7
+ * and joining them together. In most of the cases this will be
8
+ * a single piece like {type-name}, but type may go over multipe line
9
+ * ```
10
+ * @tag {function(
11
+ * number,
12
+ * string
13
+ * )}
14
+ * ```
15
+ */
16
+ export type Joiner = (parts: string[]) => string;
17
+
18
+ /**
19
+ * Shortcut for standard Joiners
20
+ * compact - trim surrounding space, replace line breaks with a single space
21
+ * preserve - concat as is
22
+ */
23
+ export type Spacing = 'compact' | 'preserve' | Joiner;
24
+
25
+ /**
26
+ * Sets splits remaining `Spec.lines[].tokes.description` into `type` and `description`
27
+ * tokens and populates Spec.type`
28
+ *
29
+ * @param {Spacing} spacing tells how to deal with a whitespace
30
+ * for type values going over multiple lines
31
+ */
32
+ export default function typeTokenizer(spacing: Spacing = 'compact'): Tokenizer {
33
+ const join = getJoiner(spacing);
34
+ return (spec: Spec): Spec => {
35
+ let curlies = 0;
36
+ let lines: [Tokens, string][] = [];
37
+
38
+ for (const [i, { tokens }] of spec.source.entries()) {
39
+ let type = '';
40
+ if (i === 0 && tokens.description[0] !== '{') return spec;
41
+
42
+ for (const ch of tokens.description) {
43
+ if (ch === '{') curlies++;
44
+ if (ch === '}') curlies--;
45
+ type += ch;
46
+ if (curlies === 0) break;
47
+ }
48
+
49
+ lines.push([tokens, type]);
50
+ if (curlies === 0) break;
51
+ }
52
+
53
+ if (curlies !== 0) {
54
+ spec.problems.push({
55
+ code: 'spec:type:unpaired-curlies',
56
+ message: 'unpaired curlies',
57
+ line: spec.source[0].number,
58
+ critical: true,
59
+ });
60
+ return spec;
61
+ }
62
+
63
+ const parts: string[] = [];
64
+ const offset = lines[0][0].postDelimiter.length;
65
+
66
+ for (const [i, [tokens, type]] of lines.entries()) {
67
+ if (type === '') continue;
68
+ tokens.type = type;
69
+ if (i > 0) {
70
+ tokens.type = tokens.postDelimiter.slice(offset) + type;
71
+ tokens.postDelimiter = tokens.postDelimiter.slice(0, offset);
72
+ }
73
+ [tokens.postType, tokens.description] = splitSpace(
74
+ tokens.description.slice(tokens.type.length)
75
+ );
76
+ parts.push(tokens.type);
77
+ }
78
+
79
+ parts[0] = parts[0].slice(1);
80
+ parts[parts.length - 1] = parts[parts.length - 1].slice(0, -1);
81
+ spec.type = join(parts);
82
+ return spec;
83
+ };
84
+ }
85
+
86
+ const trim = (x: string) => x.trim();
87
+
88
+ function getJoiner(spacing: Spacing): Joiner {
89
+ if (spacing === 'compact') return (t: string[]) => t.map(trim).join('');
90
+ else if (spacing === 'preserve') return (t: string[]) => t.join('\n');
91
+ else return spacing;
92
+ }
@@ -0,0 +1,64 @@
1
+ import { Block, Line, Tokens } from '../primitives';
2
+ import { seedTokens, isSpace } from '../util';
3
+
4
+ interface Width {
5
+ line: number;
6
+ start: number;
7
+ delimiter: number;
8
+ postDelimiter: number;
9
+ tag: number;
10
+ postTag: number;
11
+ name: number;
12
+ postName: number;
13
+ type: number;
14
+ postType: number;
15
+ description: number;
16
+ end: number;
17
+ }
18
+
19
+ const zeroWidth = {
20
+ line: 0,
21
+ start: 0,
22
+ delimiter: 0,
23
+ postDelimiter: 0,
24
+ tag: 0,
25
+ postTag: 0,
26
+ name: 0,
27
+ postName: 0,
28
+ type: 0,
29
+ postType: 0,
30
+ description: 0,
31
+ end: 0,
32
+ };
33
+
34
+ const fields = Object.keys(zeroWidth);
35
+
36
+ const repr = (x: string) => (isSpace(x) ? `{${x.length}}` : x);
37
+
38
+ const frame = (line: string[]) => '|' + line.join('|') + '|';
39
+
40
+ const align = (width: Width, tokens: Tokens): string[] =>
41
+ Object.keys(tokens).map((k) => repr(tokens[k]).padEnd(width[k]));
42
+
43
+ export default function inspect({ source }: Block): string {
44
+ if (source.length === 0) return '';
45
+
46
+ const width: Width = { ...zeroWidth };
47
+
48
+ for (const f of fields) width[f] = f.length;
49
+ for (const { number, tokens } of source) {
50
+ width.line = Math.max(width.line, number.toString().length);
51
+ for (const k in tokens)
52
+ width[k] = Math.max(width[k], repr(tokens[k]).length);
53
+ }
54
+
55
+ const lines: string[][] = [[], []];
56
+ for (const f of fields) lines[0].push(f.padEnd(width[f]));
57
+ for (const f of fields) lines[1].push('-'.padEnd(width[f], '-'));
58
+ for (const { number, tokens } of source) {
59
+ const line = number.toString().padStart(width.line);
60
+ lines.push([line, ...align(width, tokens)]);
61
+ }
62
+
63
+ return lines.map(frame).join('\n');
64
+ }
@@ -2,9 +2,6 @@ import { Block } from '../primitives';
2
2
 
3
3
  export type Transform = (Block) => Block;
4
4
 
5
- export { default as indent } from './indent';
6
- export { default as align } from './align';
7
-
8
5
  export function flow(...transforms: Transform[]): Transform {
9
6
  return (block: Block): Block =>
10
7
  transforms.reduce((block, t) => t(block), block);
package/src/util.ts CHANGED
@@ -55,6 +55,11 @@ export function seedTokens(tokens: Partial<Tokens> = {}): Tokens {
55
55
  };
56
56
  }
57
57
 
58
+ /**
59
+ * Assures Block.tags[].source contains references to the Block.source items,
60
+ * using Block.source as a source of truth. This is a counterpart of rewireSpecs
61
+ * @param block parsed coments block
62
+ */
58
63
  export function rewireSource(block: Block): Block {
59
64
  const source = block.source.reduce(
60
65
  (acc, line) => acc.set(line.number, line),
@@ -65,3 +70,18 @@ export function rewireSource(block: Block): Block {
65
70
  }
66
71
  return block;
67
72
  }
73
+
74
+ /**
75
+ * Assures Block.source contains references to the Block.tags[].source items,
76
+ * using Block.tags[].source as a source of truth. This is a counterpart of rewireSource
77
+ * @param block parsed coments block
78
+ */
79
+ export function rewireSpecs(block: Block): Block {
80
+ const source = block.tags.reduce(
81
+ (acc, spec) =>
82
+ spec.source.reduce((acc, line) => acc.set(line.number, line), acc),
83
+ new Map<number, Line>()
84
+ );
85
+ block.source = block.source.map((line) => source.get(line.number) || line);
86
+ return block;
87
+ }
@@ -60,12 +60,16 @@ function parse_spacing(source, parse, stringify, transforms) {
60
60
  * over multiple lines followed by
61
61
  * @param {string} name the name parameter
62
62
  * with multiline description
63
+ * @param {function(
64
+ * number,
65
+ * string
66
+ * )} options the options
63
67
  */
64
68
 
65
69
  const parsed = parse(source, { spacing: 'preserve' });
66
70
  const stringified = parsed[0].tags
67
- .map((tag) => `@${tag.tag} - ${tag.description}`)
68
- .join('\n');
71
+ .map((tag) => `@${tag.tag} - ${tag.description}\n\n${tag.type}`)
72
+ .join('\n----\n');
69
73
  }
70
74
 
71
75
  function parse_escaping(source, parse, stringify, transforms) {
@@ -156,11 +160,44 @@ function parse_source_exploration(source, parse, stringify, transforms) {
156
160
  .map((s) => `${pos(s.start)} - ${pos(s.end)}\n${s.source}`);
157
161
  }
158
162
 
163
+ function parse_advanced_parsing(source, parse, _, _, tokenizers) {
164
+ // Each '@tag ...' section results into Spec. Spec is computed by
165
+ // the chain of tokenizers each contributing change to the Spec.* and Spec.tags[].tokens.
166
+ // Default parse() options come with stadart tokenizers
167
+ // {
168
+ // ...,
169
+ // spacing = 'compact',
170
+ // tokenizers = [
171
+ // tokenizers.tag(),
172
+ // tokenizers.type(spacing),
173
+ // tokenizers.name(),
174
+ // tokenizers.description(spacing),
175
+ // ]
176
+ // }
177
+ // You can reorder those, or even replace any with a custom function (spec: Spec) => Spec
178
+ // This example allows to parse "@tag description" comments
179
+
180
+ /**
181
+ * @arg0 my parameter
182
+ * @arg1
183
+ * another parameter
184
+ * with a strange formatting
185
+ */
186
+
187
+ const parsed = parse(source, {
188
+ tokenizers: [tokenizers.tag(), tokenizers.description('preserve')],
189
+ });
190
+ const stringified = parsed[0].tags
191
+ .map((tag) => `@${tag.tag} - ${tag.description}`)
192
+ .join('\n');
193
+ }
194
+
159
195
  (typeof window === 'undefined' ? module.exports : window).examples = [
160
196
  parse_defaults,
161
197
  parse_line_numbering,
162
198
  parse_escaping,
163
199
  parse_spacing,
164
200
  parse_source_exploration,
201
+ parse_advanced_parsing,
165
202
  stringify_formatting,
166
203
  ];
@@ -1,9 +1,11 @@
1
- const { parse, stringify, transforms } = require('../../lib');
1
+ const { parse, stringify, transforms, tokenizers } = require('../../lib');
2
2
  const { examples } = require('./examples');
3
3
 
4
4
  const table = examples.map((fn) => [fn.name.replace(/_/g, ' '), fn]);
5
5
 
6
6
  test.each(table)('example - %s', (name, fn) => {
7
7
  const source = fn.toString();
8
- expect(() => fn(source, parse, stringify, transforms)).not.toThrow();
8
+ expect(() =>
9
+ fn(source, parse, stringify, transforms, tokenizers)
10
+ ).not.toThrow();
9
11
  });