comment-parser 1.0.0 → 1.1.2

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 (106) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +26 -19
  3. package/browser/index.js +331 -272
  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 +4 -4
  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.d.ts +2 -1
  24. package/es6/stringifier/index.js +1 -6
  25. package/es6/stringifier/inspect.d.ts +2 -0
  26. package/es6/stringifier/inspect.js +41 -0
  27. package/es6/transforms/align.js +48 -43
  28. package/es6/transforms/indent.js +11 -22
  29. package/es6/transforms/index.d.ts +0 -2
  30. package/es6/transforms/index.js +2 -10
  31. package/es6/util.d.ts +11 -0
  32. package/es6/util.js +25 -25
  33. package/jest.config.js +12 -11
  34. package/lib/index.d.ts +21 -4
  35. package/lib/index.js +24 -6
  36. package/lib/parser/block-parser.d.ts +18 -2
  37. package/lib/parser/block-parser.js +12 -12
  38. package/lib/parser/index.d.ts +4 -4
  39. package/lib/parser/index.js +26 -27
  40. package/lib/parser/source-parser.js +14 -16
  41. package/lib/parser/spec-parser.d.ts +1 -6
  42. package/lib/parser/spec-parser.js +4 -156
  43. package/lib/parser/tokenizers/description.d.ts +19 -0
  44. package/lib/parser/tokenizers/description.js +51 -0
  45. package/lib/parser/tokenizers/index.d.ts +7 -0
  46. package/lib/parser/tokenizers/index.js +2 -0
  47. package/lib/parser/tokenizers/name.d.ts +6 -0
  48. package/lib/parser/tokenizers/name.js +94 -0
  49. package/lib/parser/tokenizers/tag.d.ts +6 -0
  50. package/lib/parser/tokenizers/tag.js +27 -0
  51. package/lib/parser/tokenizers/type.d.ts +27 -0
  52. package/lib/parser/tokenizers/type.js +70 -0
  53. package/lib/stringifier/index.d.ts +2 -1
  54. package/lib/stringifier/index.js +1 -6
  55. package/lib/stringifier/inspect.d.ts +2 -0
  56. package/lib/stringifier/inspect.js +44 -0
  57. package/lib/transforms/align.js +50 -45
  58. package/lib/transforms/indent.js +12 -23
  59. package/lib/transforms/index.d.ts +0 -2
  60. package/lib/transforms/index.js +3 -13
  61. package/lib/util.d.ts +11 -0
  62. package/lib/util.js +27 -26
  63. package/migrate-1.0.md +7 -7
  64. package/package.json +1 -1
  65. package/src/index.ts +20 -2
  66. package/src/parser/block-parser.ts +19 -1
  67. package/src/parser/index.ts +22 -21
  68. package/src/parser/source-parser.ts +2 -2
  69. package/src/parser/spec-parser.ts +2 -170
  70. package/src/parser/tokenizers/description.ts +75 -0
  71. package/src/parser/tokenizers/index.ts +8 -0
  72. package/src/parser/tokenizers/name.ts +112 -0
  73. package/src/parser/tokenizers/tag.ts +30 -0
  74. package/src/parser/tokenizers/type.ts +92 -0
  75. package/src/stringifier/index.ts +3 -1
  76. package/src/stringifier/inspect.ts +64 -0
  77. package/src/transforms/align.ts +37 -12
  78. package/src/transforms/index.ts +0 -3
  79. package/src/util.ts +20 -0
  80. package/tests/e2e/examples.js +39 -2
  81. package/tests/e2e/examples.spec.js +4 -2
  82. package/tests/e2e/issue-109.spec.js +49 -0
  83. package/tests/e2e/issue-112.spec.js +20 -0
  84. package/tests/e2e/issue-113.spec.js +23 -0
  85. package/tests/e2e/issue-119.spec.js +29 -0
  86. package/tests/e2e/issue-120.spec.js +29 -0
  87. package/tests/e2e/issue-121.spec.js +102 -0
  88. package/tests/e2e/transforms.spec.js +5 -2
  89. package/tests/unit/inspect.spec.ts +36 -0
  90. package/tests/unit/{spacer.spec.ts → spacer-description-joiner.spec.ts} +6 -6
  91. package/tests/unit/spec-description-tokenizer.spec.ts +100 -7
  92. package/tests/unit/spec-name-tokenizer.spec.ts +113 -1
  93. package/tests/unit/spec-parser.spec.ts +6 -9
  94. package/tests/unit/spec-tag-tokenizer.spec.ts +1 -1
  95. package/tests/unit/spec-type-tokenizer.spec.ts +121 -1
  96. package/tests/unit/stringifier.spec.ts +0 -1
  97. package/tests/unit/transforms-align.spec.ts +80 -16
  98. package/tests/unit/util-rewire.spec.ts +107 -0
  99. package/tests/unit/util.spec.ts +0 -48
  100. package/tsconfig.es6.json +1 -1
  101. package/tsconfig.node.json +1 -1
  102. package/es6/parser/spacer.d.ts +0 -3
  103. package/es6/parser/spacer.js +0 -37
  104. package/lib/parser/spacer.d.ts +0 -3
  105. package/lib/parser/spacer.js +0 -40
  106. 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(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
+ }
@@ -1,5 +1,7 @@
1
1
  import { Block, Tokens } from '../primitives';
2
2
 
3
+ export type Stringifier = (block: Block) => string;
4
+
3
5
  function join(tokens: Tokens): string {
4
6
  return (
5
7
  tokens.start +
@@ -16,7 +18,7 @@ function join(tokens: Tokens): string {
16
18
  );
17
19
  }
18
20
 
19
- export default function getStringifier() {
21
+ export default function getStringifier(): Stringifier {
20
22
  return (block: Block): string =>
21
23
  block.source.map(({ tokens }) => join(tokens)).join('\n');
22
24
  }
@@ -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
+ }
@@ -23,15 +23,6 @@ const getWidth = (w: Width, { tokens: t }: Line) => ({
23
23
  name: Math.max(w.name, t.name.length),
24
24
  });
25
25
 
26
- // /**
27
- // * Description may go
28
- // * over multiple lines followed by @tags
29
- // *
30
- //* @my-tag {my.type} my-name description line 1
31
- // description line 2
32
- // * description line 3
33
- // */
34
-
35
26
  const space = (len: number) => ''.padStart(len, ' ');
36
27
 
37
28
  export default function align(): Transform {
@@ -62,15 +53,49 @@ export default function align(): Transform {
62
53
  tokens.start = space(w.start + 1);
63
54
  break;
64
55
  default:
65
- tokens.start = space(w.start + 3);
66
56
  tokens.delimiter = '';
57
+ tokens.start = space(w.start + 2); // compensate delimiter
58
+ }
59
+
60
+ if (!intoTags) {
61
+ tokens.postDelimiter = tokens.description === '' ? '' : ' ';
62
+ return { ...line, tokens };
63
+ }
64
+
65
+ const nothingAfter = {
66
+ delim: false,
67
+ tag: false,
68
+ type: false,
69
+ name: false,
70
+ };
71
+
72
+ if (tokens.description === '') {
73
+ nothingAfter.name = true;
74
+ tokens.postName = '';
75
+
76
+ if (tokens.name === '') {
77
+ nothingAfter.type = true;
78
+ tokens.postType = '';
79
+
80
+ if (tokens.type === '') {
81
+ nothingAfter.tag = true;
82
+ tokens.postTag = '';
83
+
84
+ if (tokens.tag === '') {
85
+ nothingAfter.delim = true;
86
+ }
87
+ }
88
+ }
67
89
  }
68
90
 
69
- if (intoTags) {
91
+ tokens.postDelimiter = nothingAfter.delim ? '' : ' ';
92
+
93
+ if (!nothingAfter.tag)
70
94
  tokens.postTag = space(w.tag - tokens.tag.length + 1);
95
+ if (!nothingAfter.type)
71
96
  tokens.postType = space(w.type - tokens.type.length + 1);
97
+ if (!nothingAfter.name)
72
98
  tokens.postName = space(w.name - tokens.name.length + 1);
73
- }
74
99
 
75
100
  return { ...line, tokens };
76
101
  }
@@ -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
+ }