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.
- package/CHANGELOG.md +13 -0
- package/README.md +30 -21
- package/browser/index.js +301 -261
- package/es6/index.d.ts +21 -4
- package/es6/index.js +21 -5
- package/es6/parser/block-parser.d.ts +18 -2
- package/es6/parser/block-parser.js +12 -12
- package/es6/parser/index.d.ts +2 -3
- package/es6/parser/index.js +25 -26
- package/es6/parser/source-parser.js +13 -15
- package/es6/parser/spec-parser.d.ts +1 -6
- package/es6/parser/spec-parser.js +4 -151
- package/es6/parser/tokenizers/description.d.ts +19 -0
- package/es6/parser/tokenizers/description.js +46 -0
- package/es6/parser/tokenizers/index.d.ts +7 -0
- package/es6/parser/tokenizers/index.js +1 -0
- package/es6/parser/tokenizers/name.d.ts +6 -0
- package/es6/parser/tokenizers/name.js +91 -0
- package/es6/parser/tokenizers/tag.d.ts +6 -0
- package/es6/parser/tokenizers/tag.js +24 -0
- package/es6/parser/tokenizers/type.d.ts +27 -0
- package/es6/parser/tokenizers/type.js +67 -0
- package/es6/stringifier/index.js +1 -6
- package/es6/stringifier/inspect.d.ts +2 -0
- package/es6/stringifier/inspect.js +41 -0
- package/es6/transforms/align.js +18 -32
- package/es6/transforms/indent.js +11 -22
- package/es6/transforms/index.d.ts +0 -2
- package/es6/transforms/index.js +2 -10
- package/es6/util.d.ts +11 -0
- package/es6/util.js +25 -25
- package/jest.config.js +12 -11
- package/lib/index.d.ts +21 -4
- package/lib/index.js +24 -6
- package/lib/parser/block-parser.d.ts +18 -2
- package/lib/parser/block-parser.js +12 -12
- package/lib/parser/index.d.ts +2 -3
- package/lib/parser/index.js +26 -27
- package/lib/parser/source-parser.js +14 -16
- package/lib/parser/spec-parser.d.ts +1 -6
- package/lib/parser/spec-parser.js +4 -156
- package/lib/parser/tokenizers/description.d.ts +19 -0
- package/lib/parser/tokenizers/description.js +51 -0
- package/lib/parser/tokenizers/index.d.ts +7 -0
- package/lib/parser/tokenizers/index.js +2 -0
- package/lib/parser/tokenizers/name.d.ts +6 -0
- package/lib/parser/tokenizers/name.js +94 -0
- package/lib/parser/tokenizers/tag.d.ts +6 -0
- package/lib/parser/tokenizers/tag.js +27 -0
- package/lib/parser/tokenizers/type.d.ts +27 -0
- package/lib/parser/tokenizers/type.js +70 -0
- package/lib/stringifier/index.js +1 -6
- package/lib/stringifier/inspect.d.ts +2 -0
- package/lib/stringifier/inspect.js +44 -0
- package/lib/transforms/align.js +20 -34
- package/lib/transforms/indent.js +12 -23
- package/lib/transforms/index.d.ts +0 -2
- package/lib/transforms/index.js +3 -13
- package/lib/util.d.ts +11 -0
- package/lib/util.js +27 -26
- package/migrate-1.0.md +7 -7
- package/package.json +1 -1
- package/src/index.ts +20 -2
- package/src/parser/block-parser.ts +19 -1
- package/src/parser/index.ts +19 -20
- package/src/parser/source-parser.ts +2 -2
- package/src/parser/spec-parser.ts +2 -170
- package/src/parser/tokenizers/description.ts +75 -0
- package/src/parser/tokenizers/index.ts +8 -0
- package/src/parser/tokenizers/name.ts +112 -0
- package/src/parser/tokenizers/tag.ts +30 -0
- package/src/parser/tokenizers/type.ts +92 -0
- package/src/stringifier/inspect.ts +64 -0
- package/src/transforms/index.ts +0 -3
- package/src/util.ts +20 -0
- package/tests/e2e/examples.js +39 -2
- package/tests/e2e/examples.spec.js +4 -2
- package/tests/e2e/issue-109.spec.js +49 -0
- package/tests/e2e/issue-112.spec.js +20 -0
- package/tests/e2e/issue-113.spec.js +23 -0
- package/tests/e2e/transforms.spec.js +5 -2
- package/tests/unit/inspect.spec.ts +36 -0
- package/tests/unit/{spacer.spec.ts → spacer-description-joiner.spec.ts} +6 -6
- package/tests/unit/spec-description-tokenizer.spec.ts +100 -7
- package/tests/unit/spec-name-tokenizer.spec.ts +113 -1
- package/tests/unit/spec-parser.spec.ts +6 -9
- package/tests/unit/spec-tag-tokenizer.spec.ts +1 -1
- package/tests/unit/spec-type-tokenizer.spec.ts +135 -1
- package/tests/unit/stringifier.spec.ts +0 -1
- package/tests/unit/transforms-align.spec.ts +1 -1
- package/tests/unit/util-rewire.spec.ts +107 -0
- package/tests/unit/util.spec.ts +0 -48
- package/tsconfig.es6.json +1 -1
- package/tsconfig.node.json +1 -1
- package/es6/parser/spacer.d.ts +0 -3
- package/es6/parser/spacer.js +0 -37
- package/lib/parser/spacer.d.ts +0 -3
- package/lib/parser/spacer.js +0 -40
- 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 {
|
|
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
|
+
}
|
package/src/transforms/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/tests/e2e/examples.js
CHANGED
|
@@ -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(() =>
|
|
8
|
+
expect(() =>
|
|
9
|
+
fn(source, parse, stringify, transforms, tokenizers)
|
|
10
|
+
).not.toThrow();
|
|
9
11
|
});
|