| import { SourceLocation } from './scanner'; |
| import { Syntax } from './syntax'; |
| |
| interface Comment { |
| type: string; |
| value: string; |
| range?: [number, number]; |
| loc?: SourceLocation; |
| } |
| |
| interface Entry { |
| comment: Comment; |
| start: number; |
| } |
| |
| interface NodeInfo { |
| node: any; |
| start: number; |
| } |
| |
| export class CommentHandler { |
| attach: boolean; |
| comments: Comment[]; |
| stack: NodeInfo[]; |
| leading: Entry[]; |
| trailing: Entry[]; |
| |
| constructor() { |
| this.attach = false; |
| this.comments = []; |
| this.stack = []; |
| this.leading = []; |
| this.trailing = []; |
| } |
| |
| insertInnerComments(node, metadata) { |
| // innnerComments for properties empty block |
| // `function a() {/** comments **\/}` |
| if (node.type === Syntax.BlockStatement && node.body.length === 0) { |
| const innerComments: Comment[] = []; |
| for (let i = this.leading.length - 1; i >= 0; --i) { |
| const entry = this.leading[i]; |
| if (metadata.end.offset >= entry.start) { |
| innerComments.unshift(entry.comment); |
| this.leading.splice(i, 1); |
| this.trailing.splice(i, 1); |
| } |
| } |
| if (innerComments.length) { |
| node.innerComments = innerComments; |
| } |
| } |
| } |
| |
| findTrailingComments(metadata) { |
| let trailingComments: Comment[] = []; |
| |
| if (this.trailing.length > 0) { |
| for (let i = this.trailing.length - 1; i >= 0; --i) { |
| const entry = this.trailing[i]; |
| if (entry.start >= metadata.end.offset) { |
| trailingComments.unshift(entry.comment); |
| } |
| } |
| this.trailing.length = 0; |
| return trailingComments; |
| } |
| |
| const last = this.stack[this.stack.length - 1]; |
| if (last && last.node.trailingComments) { |
| const firstComment = last.node.trailingComments[0]; |
| if (firstComment && firstComment.range[0] >= metadata.end.offset) { |
| trailingComments = last.node.trailingComments; |
| delete last.node.trailingComments; |
| } |
| } |
| return trailingComments; |
| } |
| |
| findLeadingComments(metadata) { |
| const leadingComments: Comment[] = []; |
| |
| let target; |
| while (this.stack.length > 0) { |
| const entry = this.stack[this.stack.length - 1]; |
| if (entry && entry.start >= metadata.start.offset) { |
| target = entry.node; |
| this.stack.pop(); |
| } else { |
| break; |
| } |
| } |
| |
| if (target) { |
| const count = target.leadingComments ? target.leadingComments.length : 0; |
| for (let i = count - 1; i >= 0; --i) { |
| const comment = target.leadingComments[i]; |
| if (comment.range[1] <= metadata.start.offset) { |
| leadingComments.unshift(comment); |
| target.leadingComments.splice(i, 1); |
| } |
| } |
| if (target.leadingComments && target.leadingComments.length === 0) { |
| delete target.leadingComments; |
| } |
| return leadingComments; |
| } |
| |
| for (let i = this.leading.length - 1; i >= 0; --i) { |
| const entry = this.leading[i]; |
| if (entry.start <= metadata.start.offset) { |
| leadingComments.unshift(entry.comment); |
| this.leading.splice(i, 1); |
| } |
| } |
| return leadingComments; |
| } |
| |
| visitNode(node, metadata) { |
| if (node.type === Syntax.Program && node.body.length > 0) { |
| return; |
| } |
| |
| this.insertInnerComments(node, metadata); |
| const trailingComments = this.findTrailingComments(metadata); |
| const leadingComments = this.findLeadingComments(metadata); |
| if (leadingComments.length > 0) { |
| node.leadingComments = leadingComments; |
| } |
| if (trailingComments.length > 0) { |
| node.trailingComments = trailingComments; |
| } |
| |
| this.stack.push({ |
| node: node, |
| start: metadata.start.offset |
| }); |
| } |
| |
| visitComment(node, metadata) { |
| const type = (node.type[0] === 'L') ? 'Line' : 'Block'; |
| const comment: Comment = { |
| type: type, |
| value: node.value |
| }; |
| if (node.range) { |
| comment.range = node.range; |
| } |
| if (node.loc) { |
| comment.loc = node.loc; |
| } |
| this.comments.push(comment); |
| |
| if (this.attach) { |
| const entry: Entry = { |
| comment: { |
| type: type, |
| value: node.value, |
| range: [metadata.start.offset, metadata.end.offset] |
| }, |
| start: metadata.start.offset |
| }; |
| if (node.loc) { |
| entry.comment.loc = node.loc; |
| } |
| node.type = type; |
| this.leading.push(entry); |
| this.trailing.push(entry); |
| } |
| } |
| |
| visit(node, metadata) { |
| if (node.type === 'LineComment') { |
| this.visitComment(node, metadata); |
| } else if (node.type === 'BlockComment') { |
| this.visitComment(node, metadata); |
| } else if (this.attach) { |
| this.visitNode(node, metadata); |
| } |
| } |
| |
| } |