Skip to content

Commit d4d62e1

Browse files
fix: ensure that sub keys can only be consumed by a single type (the first one) (#10)
This ensures that types like Object | String don't both try to enumerate the sub keys.
1 parent 6fcb117 commit d4d62e1

File tree

3 files changed

+77
-47
lines changed

3 files changed

+77
-47
lines changed

src/DocsParser.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
findContentInsideHeader,
2323
headingsAndContent,
2424
findConstructorHeader,
25+
consumeTypedKeysList,
2526
} from './markdown-helpers';
2627
import { WEBSITE_BASE_DOCS_URL, REPO_BASE_DOCS_URL } from './constants';
2728
import { extendError } from './helpers';
@@ -245,12 +246,10 @@ export class DocsParser {
245246

246247
expect(list).to.not.equal(null, `Structure file ${filePath} has no property list`);
247248

248-
const typedKeys = convertListToTypedKeys(list!);
249-
250249
return {
251250
type: 'Structure',
252251
...baseInfos[0].container,
253-
properties: typedKeys.map(typedKey => ({
252+
properties: consumeTypedKeysList(convertListToTypedKeys(list!)).map(typedKey => ({
254253
name: typedKey.key,
255254
description: typedKey.description,
256255
required: typedKey.required,

src/block-parsers.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
extractReturnType,
1313
findContentAfterHeadingClose,
1414
StripReturnTypeBehavior,
15+
consumeTypedKeysList,
1516
} from './markdown-helpers';
1617
import {
1718
MethodDocumentationBlock,
@@ -97,7 +98,7 @@ export const _headingToMethodBlock = (
9798
null,
9899
`Method ${heading.heading} has at least one parameter but no parameter type list`,
99100
);
100-
parameters = convertListToTypedKeys(list).map(typedKey => ({
101+
parameters = consumeTypedKeysList(convertListToTypedKeys(list)).map(typedKey => ({
101102
name: typedKey.key,
102103
description: typedKey.description,
103104
required: typedKey.required,
@@ -183,8 +184,7 @@ export const _headingToEventBlock = (heading: HeadingContent): EventDocumentatio
183184
) {
184185
const list = findNextList(heading.content);
185186
if (list) {
186-
const typedKeys = convertListToTypedKeys(list);
187-
parameters = typedKeys.map(typedKey => ({
187+
parameters = consumeTypedKeysList(convertListToTypedKeys(list)).map(typedKey => ({
188188
name: typedKey.key,
189189
description: typedKey.description,
190190
...typedKey.type,

src/markdown-helpers.ts

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ export const getTopLevelGenericType = (typeString: string) => {
208208
export const rawTypeToTypeInformation = (
209209
rawType: string,
210210
relatedDescription: string,
211-
subTypedKeys: TypedKey[] | null,
211+
subTypedKeys: TypedKeyList | null,
212212
): TypeInformation => {
213213
// Handle the edge case of "null"
214214
if (rawType === 'null' || rawType === '`null`') {
@@ -254,42 +254,45 @@ export const rawTypeToTypeInformation = (
254254
return {
255255
collection,
256256
type: 'Function',
257-
parameters: subTypedKeys
258-
? subTypedKeys.map<MethodParameterDocumentation>(typedKey => ({
259-
name: typedKey.key,
260-
description: typedKey.description,
261-
required: typedKey.required,
262-
...typedKey.type,
263-
}))
264-
: [],
257+
parameters:
258+
subTypedKeys && !subTypedKeys.consumed
259+
? consumeTypedKeysList(subTypedKeys).map<MethodParameterDocumentation>(typedKey => ({
260+
name: typedKey.key,
261+
description: typedKey.description,
262+
required: typedKey.required,
263+
...typedKey.type,
264+
}))
265+
: [],
265266
returns: null,
266267
};
267268
} else if (typeString === 'Object') {
268269
return {
269270
collection,
270271
type: 'Object',
271-
properties: subTypedKeys
272-
? subTypedKeys.map<PropertyDocumentationBlock>(typedKey => ({
273-
name: typedKey.key,
274-
description: typedKey.description,
275-
required: typedKey.required,
276-
additionalTags: typedKey.additionalTags,
277-
...typedKey.type,
278-
}))
279-
: [],
272+
properties:
273+
subTypedKeys && !subTypedKeys.consumed
274+
? consumeTypedKeysList(subTypedKeys).map<PropertyDocumentationBlock>(typedKey => ({
275+
name: typedKey.key,
276+
description: typedKey.description,
277+
required: typedKey.required,
278+
additionalTags: typedKey.additionalTags,
279+
...typedKey.type,
280+
}))
281+
: [],
280282
};
281283
} else if (typeString === 'String') {
282284
return {
283285
collection,
284286
type: 'String',
285-
possibleValues: subTypedKeys
286-
? subTypedKeys.map<PossibleStringValue>(typedKey => ({
287-
value: typedKey.key,
288-
description: typedKey.description,
289-
}))
290-
: relatedDescription
291-
? extractStringEnum(relatedDescription)
292-
: null,
287+
possibleValues:
288+
subTypedKeys && !subTypedKeys.consumed
289+
? consumeTypedKeysList(subTypedKeys).map<PossibleStringValue>(typedKey => ({
290+
value: typedKey.key,
291+
description: typedKey.description,
292+
}))
293+
: relatedDescription
294+
? extractStringEnum(relatedDescription)
295+
: null,
293296
};
294297
}
295298

@@ -303,15 +306,16 @@ export const rawTypeToTypeInformation = (
303306
return {
304307
...info,
305308
type: 'Object',
306-
properties: subTypedKeys
307-
? subTypedKeys.map<PropertyDocumentationBlock>(typedKey => ({
308-
name: typedKey.key,
309-
description: typedKey.description,
310-
required: typedKey.required,
311-
additionalTags: typedKey.additionalTags,
312-
...typedKey.type,
313-
}))
314-
: [],
309+
properties:
310+
subTypedKeys && !subTypedKeys.consumed
311+
? consumeTypedKeysList(subTypedKeys).map<PropertyDocumentationBlock>(typedKey => ({
312+
name: typedKey.key,
313+
description: typedKey.description,
314+
required: typedKey.required,
315+
additionalTags: typedKey.additionalTags,
316+
...typedKey.type,
317+
}))
318+
: [],
315319
};
316320
}
317321
return info;
@@ -328,8 +332,8 @@ export const rawTypeToTypeInformation = (
328332
parameters:
329333
// If no param types are provided in the <A, B, C> syntax then we should fallback to the normal one
330334
genericProvidedParams.length === 0
331-
? subTypedKeys
332-
? subTypedKeys.map<MethodParameterDocumentation>(typedKey => ({
335+
? subTypedKeys && !subTypedKeys.consumed
336+
? consumeTypedKeysList(subTypedKeys).map<MethodParameterDocumentation>(typedKey => ({
333337
name: typedKey.key,
334338
description: typedKey.description,
335339
required: typedKey.required,
@@ -428,7 +432,7 @@ export const extractReturnType = (
428432
}
429433

430434
const list = findNextList(tokens);
431-
let typedKeys: null | TypedKey[] = null;
435+
let typedKeys: null | TypedKeyList = null;
432436
if (list) {
433437
try {
434438
typedKeys = convertListToTypedKeys(tokens);
@@ -555,6 +559,11 @@ type TypedKey = {
555559
additionalTags: DocumentationTag[];
556560
};
557561

562+
type TypedKeyList = {
563+
keys: TypedKey[];
564+
consumed: boolean;
565+
};
566+
558567
type List = { items: ListItem[] };
559568
type ListItem = { tokens: Token[]; nestedList: List | null };
560569

@@ -592,6 +601,24 @@ const getNestedList = (rawTokens: Token[]): List => {
592601
return rootList;
593602
};
594603

604+
const unconsumedTypedKeyList = <T extends TypedKey[] | null>(
605+
keys: T,
606+
): T extends null ? null : TypedKeyList => {
607+
return keys
608+
? {
609+
consumed: false,
610+
keys,
611+
}
612+
: (null as any);
613+
};
614+
615+
export const consumeTypedKeysList = (list: TypedKeyList) => {
616+
if (list.consumed)
617+
throw new Error('Attempted to consume a typed keys list that has already been consumed');
618+
list.consumed = true;
619+
return list.keys;
620+
};
621+
595622
const convertNestedListToTypedKeys = (list: List): TypedKey[] => {
596623
const keys: TypedKey[] = [];
597624

@@ -651,7 +678,11 @@ const convertNestedListToTypedKeys = (list: List): TypedKey[] => {
651678
const tagMatch = tagMatcher.exec(rawType);
652679
const cleanedType = rawType.replace(/ ?\(optional\) ?/i, '').replace(/_.+?_/g, '');
653680
const subTypedKeys = item.nestedList ? convertNestedListToTypedKeys(item.nestedList) : null;
654-
const type = rawTypeToTypeInformation(cleanedType.trim(), rawDescription, subTypedKeys);
681+
const type = rawTypeToTypeInformation(
682+
cleanedType.trim(),
683+
rawDescription,
684+
unconsumedTypedKeyList(subTypedKeys),
685+
);
655686

656687
keys.push({
657688
type,
@@ -665,8 +696,8 @@ const convertNestedListToTypedKeys = (list: List): TypedKey[] => {
665696
return keys;
666697
};
667698

668-
export const convertListToTypedKeys = (listTokens: Token[]): TypedKey[] => {
699+
export const convertListToTypedKeys = (listTokens: Token[]): TypedKeyList => {
669700
const list = getNestedList(listTokens);
670701

671-
return convertNestedListToTypedKeys(list);
702+
return unconsumedTypedKeyList(convertNestedListToTypedKeys(list));
672703
};

0 commit comments

Comments
 (0)