Skip to content

Commit 0f7a337

Browse files
committed
Add parent comment parsing and tests
1 parent c02d19c commit 0f7a337

9 files changed

Lines changed: 396 additions & 77 deletions

File tree

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { AnnotationComment } from './types'
2-
import { parseAnnotationTags } from '../internal/tag-parser'
2+
import { parseAnnotationTags } from '../parsers/annotation-tags'
3+
import { parseParentComment } from '../parsers/parent-comment'
34

45
export type ParseAnnotationCommentsOptions = {
56
codeLines: string[]
@@ -10,36 +11,18 @@ export function parseAnnotationComments(options: ParseAnnotationCommentsOptions)
1011
const { codeLines, validateAnnotationName } = options
1112
const annotationComments: AnnotationComment[] = []
1213

13-
// Parse annotation tags
14+
// Find annotation tags
1415
const annotationTags = parseAnnotationTags({ codeLines })
16+
annotationTags.forEach((tag) => {
17+
// Ensure that the current annotation tag has not been ignored by an ´ignore-tags` directive. If it has, it will skip the tag and continue searching
18+
// If given, call the `validateAnnotationName` handler function to check if the annotation name is valid. If this function returns `false`, skip the tag and continue searching
1519

16-
// Go through all tags and...
17-
// - Ensure that the current annotation tag has not been ignored by an ´ignore-tags` directive. If it has, it will skip the tag and continue searching
18-
// - If given, call the `validateAnnotationName` handler function to check if the annotation name is valid. If this function returns `false`, skip the tag and continue searching
19-
// - **Handle the current annotation tag if it's inside a single-line comment:** Try to find the beginning sequence of a single-line comment directly before the annotation tag, with no non-whitespace character before and after the beginning sequence. If found, it will:
20-
// - Mark the location of the beginning sequence as beginning of the comment
21-
// - Support chaining of single-line comments on the same line
22-
// - After the current annotation tag, look for a repetition of the same comment beginning sequence + annotation tag syntax. If found, this is a case of chaining, so mark the location of the next beginning sequence as the end of the current comment.
23-
// - If no chaining is found, mark the end of the line as the current end of the comment (this may change later)
24-
// - Add any text after the annotation tag until the current end of the comment to the annotation's **contents**
25-
// - If there was only whitespace before the beginning of the comment (= the comment was on its own line), and no chaining was detected (= end of comment matches end of line), try to expand the comment end location and annotation content to all subsequent lines until a line is found that either doesn't start with the same single-line comment beginning sequence (only preceded by optional whitespace characters), that starts with another valid annotation tag, or that has `---` as its only text content.
26-
// - End processing the current annotation tag and continue searching for the next one
27-
// - **Handle the current annotation tag if it's inside a multi-line comment:** No single-line comment was found, so now try to find a matching pair of beginning and ending sequence of a supported multi-line comment syntax around the match:
28-
// - Walk backwards, passing each character into an array of parser functions that are each responsible for one supported comment syntax. If a parser function returns a definite result, which can either be a match or a failure, stop calling this parser.
29-
// - In the JSDoc parser, on the first processed line, allow whitespace and require either a single `*` character or the opening sequence `/**` surrounded by whitespace to be present before the tag. If not, return a failure. If the opening is found, return a match. Otherwise, keep going with all previous lines and expect the same, except that there now can be arbitrary other content between the mandatory `*` and the beginning of the line.
30-
// - In all other parsers, on the first processed line, allow only whitespace or the opening sequence surrounded by whitespace to be present before the tag. If not, return a failure. If the opening is found, return a match. Otherwise, keep going with all previous lines, but now also allow other arbitrary content. If the beginning of the code is reached, return a failure.
31-
// - If none of the parsers returned a match, skip processing the current annotation tag and continue searching for the next one
32-
// - Otherwise, walk forwards, passing each character into a new array of parser functions that are each responsible for one supported multi-line comment syntax. If a parser function returns a definite result, which can either be a match or a failure, stop calling this parser.
33-
// - In the JSDoc parser, on the first processed line, allow arbitrary content or the closing sequence `*/` surrounded by whitespace. If the closing is found, return a match. Otherwise, keep going with all subsequent lines, and either expect whitespace followed by a mantatory `*` and then arbitrary content. If the closing sequence surrounded by whitespace is encountered at any point, return a match. If the end of the code is reached, return a failure.
34-
// - In all other parsers, just accept any content while looking for the closing sequence surrounded by whitespace on all lines. If it is found, return a match. If the end of the code is reached, return a failure.
35-
// - Now filter the backwards and forwards results, removing any non-pairs. If the opening and closing sequences of multiple pairs overlap, only keep the longest sequence (this ensures that we're capturing `{ /* */ }` instead of just the inner `/* */`). Finally, keep only the innermost pair.
36-
// - If no pair was found, skip processing the current annotation tag and continue searching for the next one
37-
// - Otherwise:
38-
// - Check rule "Comments must not be placed between code on the same line"
39-
// - If the comment starts and ends on the same line, and there is non-whitespace content both before and after the comment, skip processing the current annotation tag and continue searching for the next one
40-
// - Check rule "Comments spanning multiple lines must not share any lines with code"
41-
// - If the comment starts and ends on different lines, and there is non-whitespace content either before the start or after the end of the comment, skip processing the current annotation tag and continue searching for the next one
42-
// - Finish processing the current annotation tag and continue searching for the next one
20+
// Attempt to find a comment that the current annotation tag is located in
21+
const comment = parseParentComment({ codeLines, tag })
22+
if (!comment) return
23+
24+
// If a comment was found, add the tag and comment to the list of annotation comments
25+
})
4326

4427
return annotationComments
4528
}

packages/annotation-comments/src/core/types.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
export type AnnotationComment = {
22
tag: AnnotationTag
33
contents: string[]
4-
commentRange: CodeRange
5-
contentRanges: CodeRange[]
6-
targetRanges: CodeRange[]
4+
commentRange: SourceRange
5+
contentRanges: SourceRange[]
6+
targetRanges: SourceRange[]
77
}
88

99
export type AnnotationTag = {
@@ -42,24 +42,25 @@ export type AnnotationTag = {
4242
relativeTargetRange?: number | undefined
4343
rawTag: string
4444
/**
45-
* The tag's location within the parsed code.
45+
* The tag's range within the parsed source code.
4646
*/
47-
location: CodeRange
47+
range: SourceRange
4848
}
4949

50-
export type CodeRange = {
51-
/** Zero-based index of the range's starting line. */
52-
startLineIndex: number
53-
/** Zero-based index of the range's ending line. */
54-
endLineIndex: number
50+
export type SourceLocation = {
51+
/** Zero-based line index. */
52+
line: number
5553
/**
56-
* Zero-based index of the range's starting column.
57-
* If not provided, the range covers the full starting line.
58-
*/
59-
startColIndex?: number | undefined
60-
/**
61-
* Zero-based index of the range's ending column.
62-
* If not provided, the range covers the full ending line.
54+
* Zero-based column index inside the line.
55+
*
56+
* If not provided, the location references the full line.
6357
*/
64-
endColIndex?: number | undefined
58+
column?: number | undefined
59+
}
60+
61+
export type SourceRange = {
62+
/** The beginning (line & optional column) of the range. */
63+
start: SourceLocation
64+
/** The end (line & optional column) of the range. */
65+
end: SourceLocation
6566
}

packages/annotation-comments/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export type * from './core/types'
22
export * from './core/parse'
33
export * from './core/remove'
44

5-
export * from './internal/tag-parser'
5+
export * from './parsers/annotation-tags'
6+
export * from './parsers/parent-comment'

packages/annotation-comments/src/internal/regexps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { coerceError } from './errors'
77
* If supported by the platform this code is running on, it will also set the `d` flag that
88
* enables capture group indices.
99
*/
10-
export function parseAsGlobalRegExp(pattern: string | RegExp, extraFlags?: string): RegExp {
10+
export function createGlobalRegExp(pattern: string | RegExp, extraFlags?: string): RegExp {
1111
let regExp: RegExp | undefined
1212
try {
1313
// Try to use regular expressions with capture group indices

packages/annotation-comments/src/internal/tag-parser.ts renamed to packages/annotation-comments/src/parsers/annotation-tags.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { AnnotationTag } from '../core/types'
2-
import { getEscapeSequenceRegExp } from './escaping'
3-
import { parseAsGlobalRegExp } from './regexps'
2+
import { getEscapeSequenceRegExp } from '../internal/escaping'
3+
import { createGlobalRegExp } from '../internal/regexps'
44

5-
type ParseAnnotationTagsOptions = {
5+
export type ParseAnnotationTagsOptions = {
66
codeLines: string[]
77
}
88

@@ -91,7 +91,7 @@ function parseTargetSearchQuery(rawTargetSearchQuery: string | undefined): strin
9191

9292
// If the delimiter was a slash, try to parse the value as a regular expression and return it
9393
if (delimiter === '/') {
94-
return parseAsGlobalRegExp(unescapedQuery)
94+
return createGlobalRegExp(unescapedQuery)
9595
}
9696

9797
// Otherwise, return the unescaped query as a string
@@ -115,11 +115,9 @@ export function parseAnnotationTags(options: ParseAnnotationTagsOptions): Annota
115115
targetSearchQuery,
116116
relativeTargetRange: relativeTargetRange !== undefined ? Number(relativeTargetRange) : undefined,
117117
rawTag,
118-
location: {
119-
startLineIndex: lineIndex,
120-
endLineIndex: lineIndex,
121-
startColIndex,
122-
endColIndex,
118+
range: {
119+
start: { line: lineIndex, column: startColIndex },
120+
end: { line: lineIndex, column: endColIndex },
123121
},
124122
})
125123
})

0 commit comments

Comments
 (0)