| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as CodeMirror from '../../../third_party/codemirror.next/codemirror.next.js'; |
| |
| const LINE_COMMENT_PATTERN = /^(?:\/\/|#)\s*/gm; |
| const BLOCK_COMMENT_START_PATTERN = /^\/\*+\s*/; |
| const BLOCK_COMMENT_END_PATTERN = /\s*\*+\/$/; |
| const BLOCK_COMMENT_LINE_PREFIX_PATTERN = /^\s*\*\s?/; |
| |
| export interface CommentNodeInfo { |
| text: string; |
| to: number; |
| } |
| |
| function findLastNonWhitespacePos(state: CodeMirror.EditorState, cursorPosition: number): number { |
| const line = state.doc.lineAt(cursorPosition); |
| const textBefore = line.text.substring(0, cursorPosition - line.from); |
| const effectiveEnd = line.from + textBefore.trimEnd().length; |
| return effectiveEnd; |
| } |
| |
| function resolveCommentNode(state: CodeMirror.EditorState, cursorPosition: number): CodeMirror.SyntaxNode|undefined { |
| const tree = CodeMirror.syntaxTree(state); |
| const lookupPos = findLastNonWhitespacePos(state, cursorPosition); |
| // Find the innermost syntax node at the last non-whitespace character position. |
| // The bias of -1 makes it check the character to the left of the position. |
| const node = tree.resolveInner(lookupPos, -1); |
| const nodeType = node.type.name; |
| // Check if the node type is a comment |
| if (nodeType.includes('Comment')) { |
| if (!nodeType.includes('BlockComment')) { |
| return node; |
| } |
| // An unclosed block comment can result in the parser inserting an error. |
| let hasInternalError = false; |
| tree.iterate({ |
| from: node.from, |
| to: node.to, |
| enter: n => { |
| if (n.type.isError) { |
| hasInternalError = true; |
| return false; |
| } |
| return true; |
| }, |
| }); |
| return hasInternalError ? undefined : node; |
| } |
| return; |
| } |
| |
| function extractBlockCommentText(rawText: string): string|undefined { |
| // Remove /* and */, whitespace, and common leading asterisks on new lines |
| if (!rawText.match(BLOCK_COMMENT_START_PATTERN)) { |
| return; |
| } |
| let cleaned = rawText.replace(BLOCK_COMMENT_START_PATTERN, ''); |
| if (!cleaned.match(BLOCK_COMMENT_END_PATTERN)) { |
| return; |
| } |
| cleaned = cleaned.replace(BLOCK_COMMENT_END_PATTERN, ''); |
| // Remove leading " * " from multi-line block comments |
| cleaned = cleaned.split('\n').map(line => line.replace(BLOCK_COMMENT_LINE_PREFIX_PATTERN, '')).join('\n').trim(); |
| return cleaned; |
| } |
| |
| function extractLineComment(node: CodeMirror.SyntaxNode, state: CodeMirror.EditorState): CommentNodeInfo|undefined { |
| let firstNode = node; |
| let lastNode = node; |
| |
| let prev = node.prevSibling; |
| while (prev?.type.name.includes('LineComment')) { |
| firstNode = prev; |
| prev = prev.prevSibling; |
| } |
| |
| let next = node.nextSibling; |
| while (next?.type.name.includes('LineComment')) { |
| lastNode = next; |
| next = next.nextSibling; |
| } |
| |
| // Extract all lines between the first and last identified node |
| const fullRawText = state.doc.sliceString(firstNode.from, lastNode.to); |
| |
| // Process each line to remove prefixes (// or #) |
| const concatenatedText = fullRawText.replaceAll(LINE_COMMENT_PATTERN, '').replace(/\n\s*\n/g, '\n').trim(); |
| |
| return concatenatedText ? {text: concatenatedText, to: lastNode.to} : undefined; |
| } |
| |
| export class AiCodeGenerationParser { |
| static extractCommentNodeInfo(state: CodeMirror.EditorState, cursorPosition: number): CommentNodeInfo|undefined { |
| const node = resolveCommentNode(state, cursorPosition); |
| if (!node) { |
| return; |
| } |
| const nodeType = node.type.name; |
| const rawText = state.doc.sliceString(node.from, node.to); |
| let text = ''; |
| if (nodeType.includes('LineComment')) { |
| return extractLineComment(node, state); |
| } |
| if (nodeType.includes('BlockComment')) { |
| text = extractBlockCommentText(rawText) ?? ''; |
| } else { |
| text = rawText; |
| } |
| |
| if (!Boolean(text)) { |
| return; |
| } |
| |
| return {text, to: node.to}; |
| } |
| } |