浏览代码

doc: make comment writer self-commented

KernelDeimos 5 月之前
父节点
当前提交
ac44aaf393
共有 1 个文件被更改,包括 122 次插入4 次删除
  1. 122 4
      tools/comment-writer/main.js

+ 122 - 4
tools/comment-writer/main.js

@@ -62,11 +62,32 @@ context.config = JSON.parse(
     fs.readFileSync('config.json')
 );
 
+
+/**
+* @class AI
+* @description A class that handles interactions with the Puter API for AI-powered chat completions.
+* This class provides an interface to make requests to the Puter chat completion service,
+* handling authentication and message formatting. It supports various AI models through
+* the puter-chat-completion driver interface.
+*/
 class AI {
     constructor (context) {
         //
     }
     
+
+    /**
+    * Sends a chat completion request to the Puter API and returns the response message.
+    * 
+    * @param {Object} params - The parameters for the completion request
+    * @param {Array} params.messages - Array of message objects to send to the API
+    * @param {Object} params.driver_params - Additional parameters for the driver interface
+    * @returns {Promise<Object>} The response message from the API
+    * 
+    * Makes a POST request to the configured API endpoint with the provided messages and
+    * driver parameters. Authenticates using the configured auth token and returns the
+    * message content from the response.
+    */
     async complete ({ messages, driver_params }) {
         const response = await axi.post(`${context.config.api_url}/drivers/call`, {
             interface: 'puter-chat-completion',
@@ -88,6 +109,7 @@ class AI {
 }
 
 const ai_message_to_lines = text => {
+    // Extract text content from message object, handling various formats
     while ( typeof text === 'object' ) {
         if ( Array.isArray(text) ) text = text[0];
         else if ( text.content ) text = text.content;
@@ -100,10 +122,14 @@ const ai_message_to_lines = text => {
     return text.split('\n');
 }
 
-class CommentWriter {
-    //
-}
-
+/**
+* @class JavascriptFileProcessor
+* @description A class responsible for processing JavaScript source files to identify and extract
+* various code definitions and structures. It analyzes the file content line by line using
+* configurable pattern matchers to detect classes, methods, functions, control structures,
+* and constants. The processor maintains context and parameters for consistent processing
+* across multiple files.
+*/
 class JavascriptFileProcessor {
     constructor (context, parameters) {
         this.context = context;
@@ -112,12 +138,15 @@ class JavascriptFileProcessor {
     
     process (lines) {
         const definitions = [];
+        // Collect definitions by iterating through each line
         for ( let i = 0 ; i < lines.length ; i++ ) {
             const line = lines[i];
+            // Iterate through each line in the file
             for ( const matcher of this.parameters.definition_matchers ) {
                 const match = matcher.pattern.exec(line);
                 console.log('match object', match);
 
+                // Check if there is a match for any of the definition patterns
                 if ( match ) {
                     definitions.push({
                         ...matcher.handler(match),
@@ -160,6 +189,14 @@ const js_processor = new JavascriptFileProcessor(context, {
         {
             name: 'if',
             pattern: /^\s*if\s*\(.*\)\s*{/,
+            /**
+            * Matches code patterns against a line to identify if it's an if statement
+            * @param {string} line - The line of code to check
+            * @returns {Object} Returns an object with type: 'if' if pattern matches
+            * @description Identifies if statements by matching the pattern /^\s*if\s*\(.*\)\s*{/
+            * This handles basic if statement syntax with optional whitespace and any condition
+            * within the parentheses
+            */
             handler: () => {
                 return { type: 'if' };
             }
@@ -167,6 +204,14 @@ const js_processor = new JavascriptFileProcessor(context, {
         {
             name: 'while',
             pattern: /^\s*while\s*\(.*\)\s*{/,
+            /**
+            * Matches lines that begin with a while loop structure.
+            * @param {void} - Takes no parameters
+            * @returns {Object} Returns an object with type: 'while' to indicate this is a while loop definition
+            * @description Used by the definition matcher system to identify while loop structures in code.
+            * The pattern looks for lines that start with optional whitespace, followed by 'while',
+            * followed by parentheses containing any characters, and ending with an opening curly brace.
+            */
             handler: () => {
                 return { type: 'while' };
             }
@@ -174,6 +219,11 @@ const js_processor = new JavascriptFileProcessor(context, {
         {
             name: 'for',
             pattern: /^\s*for\s*\(.*\)\s*{/,
+            /**
+            * Matches for loop patterns in code and returns a 'for' type definition.
+            * Used by the JavascriptFileProcessor to identify for loop structures.
+            * @returns {Object} An object with type 'for' indicating a for loop was found
+            */
             handler: () => {
                 return { type: 'for' };
             }
@@ -195,9 +245,11 @@ const js_processor = new JavascriptFileProcessor(context, {
             pattern: /^\s*[A-Za-z_\$]+.*\(\).*{/,
             handler: (match) => {
                 const [ , name ] = match;
+                // Extract method name from match array and handle special cases for 'if' and 'while'
                 if ( name === 'if' ) {
                     return { type: 'if' };
                 }
+                // Check if the name is 'while' and return appropriate type
                 if ( name === 'while' ) {
                     return { type: 'while' };
                 }
@@ -248,6 +300,16 @@ const js_processor = new JavascriptFileProcessor(context, {
     ],
 });
 
+
+/**
+* Creates a limited view of the code file by showing specific ranges around key lines.
+* Takes an array of lines and key places (anchors with context ranges) and returns
+* a formatted string showing relevant code sections with line numbers and descriptions.
+* Merges overlapping ranges to avoid duplication.
+* @param {string[]} lines - Array of code lines from the file
+* @param {Object[]} key_places - Array of objects defining important locations and context
+* @returns {string} Formatted string containing the limited code view
+*/
 const create_limited_view = (lines, key_places) => {
     // Sort key places by starting line
     key_places.sort((a, b) => {
@@ -259,6 +321,7 @@ const create_limited_view = (lines, key_places) => {
     const visible_ranges = [];
     
     // Create visible ranges for each key place
+    // Create visible ranges for each key place in the limited view
     for ( const key_place of key_places ) {
         const anchor = key_place.anchor;
         const lines_above = key_place.lines_above;
@@ -278,12 +341,14 @@ const create_limited_view = (lines, key_places) => {
     // Merge overlapping visible ranges
     const merged_ranges = [];
     
+    // Iterate through each visible range and merge overlapping ones
     for ( const range of visible_ranges ) {
         range.comments = [{
             anchor: range.anchor,
             text: range.comment
         }];
 
+        // If no merged ranges exist yet, add this range as the first one
         if ( ! merged_ranges.length ) {
             merged_ranges.push(range);
             continue;
@@ -291,6 +356,7 @@ const create_limited_view = (lines, key_places) => {
         
         const last_range = merged_ranges[merged_ranges.length - 1];
         
+        // Check if the current range overlaps with the last range in merged_ranges
         if ( last_range.end >= range.start ) {
             last_range.end = Math.max(last_range.end, range.end);
             last_range.comments.push({
@@ -306,6 +372,7 @@ const create_limited_view = (lines, key_places) => {
     let limited_view = '';
     
     let previous_visible_range = null;
+    // Iterate through visible ranges and add line numbers and comments
     for ( let i = 0 ; i < lines.length ; i++ ) {
         const line = lines[i];
         
@@ -313,7 +380,9 @@ const create_limited_view = (lines, key_places) => {
         
         if ( i === 22 ) debugger;
 
+        // Iterate through merged ranges to find which range contains the current line
         for ( const range of merged_ranges ) {
+            // Check if current line is within any of the merged ranges
             if ( i >= range.start && i < range.end ) {
                 visible_range = range;
                 break;
@@ -322,17 +391,21 @@ const create_limited_view = (lines, key_places) => {
         
         // console.log('visible_range', visible_range, i);
         
+        // Check if this line is visible in the current range
         if ( visible_range === null ) {
             continue;
         }
         
+        // Check if visible range is different from previous range
         if ( visible_range !== previous_visible_range ) {
             if ( i !== 0 ) limited_view += '\n';
+            // Check if we're starting a new visible range and add appropriate header
             if ( visible_range.comments.length === 1 ) {
                 const comment = visible_range.comments[0];
                 limited_view += `window around line ${comment.anchor}: ${comment.text}\n`;
             } else {
                 limited_view += `window around lines ${visible_range.comments.length} key lines:\n`;
+                // Iterate through visible range comments and add them to the limited view
                 for ( const comment of visible_range.comments ) {
                     limited_view += `- line ${comment.anchor}: ${comment.text}\n`;
                 }
@@ -358,14 +431,23 @@ const create_limited_view = (lines, key_places) => {
  *    lines: [ 'comment line 1', 'comment line 2', ... ]
  * }
  */
+/**
+* Injects comments into an array of code lines at specified positions
+* @param {string[]} lines - Array of original file lines
+* @param {Object[]} comments - Array of comment objects specifying where and what to inject
+* @param {number} comments[].position - Line number where comment should be inserted
+* @param {string[]} comments[].lines - Array of comment text lines to insert
+*/
 const inject_comments = (lines, comments) => {
     // Sort comments in reverse order
     comments.sort((a, b) => b.position - a.position);
     
     // Inject comments into lines
+    // Inject comments into lines array based on comment objects
     for ( const comment of comments ) {
         // AI might have been stupid and added a comment above a blank line,
         // despite that we told it not to do that. So we need to adjust the position.
+        // Adjust comment position if it would be above a blank line
         while ( comment.position < lines.length && ! lines[comment.position].trim() ) {
             comment.position++;
         }
@@ -378,7 +460,9 @@ const inject_comments = (lines, comments) => {
         // If the first line of the comment lines starts with '/*`, ensure there is
         // a blank line above it.
         
+        // Check if comment starts with '/*' to ensure proper spacing above JSDoc comments
         if ( comment_lines[0].trim().startsWith('/*') ) {
+            // Check if comment starts with JSDoc style to add blank line above
             if ( comment.position > 0 && lines[comment.position - 1].trim() === '' ) {
                 lines.splice(comment.position, 0, '');
             }
@@ -396,6 +480,14 @@ textutil.format = text => {
 
 context.ai = new AI(context);
 
+
+/**
+* Creates a new AI instance for handling chat completions
+* @param {Object} context - The application context object
+* @description Initializes an AI instance that interfaces with the Puter chat completion API.
+* The AI instance is used to generate comments and other text responses through the
+* chat completion interface.
+*/
 const main = async () => {
     // const message = await context.ai.complete({
     //     messages: [
@@ -436,10 +528,12 @@ const main = async () => {
     for await ( const value of walk_iter ) {
         i++;
         if ( i == 12 ) process.exit(0);
+        // Exit after processing 12 files
         if ( value.is_dir ) {
             console.log('directory:', value.path);
             continue;
         }
+        // Check if file is not a JavaScript file and skip it
         if ( ! value.name.endsWith('.js') ) {
             continue;
         }
@@ -447,9 +541,11 @@ const main = async () => {
         const lines = fs.readFileSync(value.path, 'utf8').split('\n');
         
         let metadata, has_metadata_line = false;
+        // Check if metadata line exists and parse it
         if ( lines[0].startsWith('// METADATA // ') ) {
             has_metadata_line = true;
             metadata = JSON.parse(lines[0].slice('// METADATA // '.length));
+            // Check if metadata exists and has been parsed from the first line
             if ( metadata['ai-commented'] ) {
                 console.log('File was already commented by AI; skipping...');
                 continue;
@@ -457,12 +553,14 @@ const main = async () => {
         }
         
         let refs = null;
+        // Check if there are any references in the metadata
         if ( metadata['ai-refs'] ) {
             const relative_file_paths = metadata['ai-refs'];
             // name of file is the key, value is the contents
             const references = {};
             
             let n  = 0;
+            // Iterate through each relative file path in the metadata
             for ( const relative_file_path of relative_file_paths ) {
                 n++;
                 const full_path = path_.join(path_.dirname(value.path), relative_file_path);
@@ -470,6 +568,7 @@ const main = async () => {
                 references[relative_file_path] = ref_text;
             }
             
+            // Check if there are any references in the metadata and process them
             if ( n === 1 ) {
                 refs = dedent(`
                     The following documentation contains relevant information about the code.
@@ -483,6 +582,7 @@ const main = async () => {
                     The code will follow after a number of documentation files.
                 `);
                 
+                // Iterate through each key in the references object
                 for ( const key of Object.keys(references) ) {
                     refs += '\n\n' + dedent(references[key]);
                 }
@@ -501,10 +601,12 @@ const main = async () => {
         })
         // const action = 'generate';
         
+        // Check if user wants to exit the program
         if ( action.action === 'exit' ) {
             break;
         }
         
+        // Skip if user chose to exit
         if ( action.action === 'skip' ) {
             continue;
         }
@@ -523,6 +625,7 @@ const main = async () => {
             lines_below: 2,
             comment: `Bottom of ${value.name}`
         });
+        // Iterate through each definition and add comments based on its type
         for ( const definition of definitions ) {
             key_places.push({
                 anchor: definition.line,
@@ -563,7 +666,9 @@ const main = async () => {
         });
         const numbers = message.content.split(',').map(n => Number(n));
 
+        // Iterate through each number in the array of line numbers
         for ( const n of numbers ) {
+            // Check if the line number is valid and not NaN before adding comment
             if ( Number.isNaN(n) ) {
                 console.log('Invalid number:', n);
                 continue;
@@ -575,10 +680,12 @@ const main = async () => {
         }
         */
 
+        // Iterate through each definition to add comments
         for ( const def of definitions ) {
             console.log('def?', def);
             let instruction = '';
             
+            // Check if the line starts with an if statement and has curly braces
             if ( def.type === 'class' ) {
                 instruction = dedent(`
                     Since the comment is going above a class definition, please write a JSDoc style comment.
@@ -586,6 +693,7 @@ const main = async () => {
                 `);
             }
 
+            // Check if comment is for an if/while/for control structure
             if ( def.type === 'if' || def.type === 'while' || def.type === 'for' ) {
                 if ( metadata['comment-verbosity'] !== 'high' ) continue;
                 instruction = dedent(`
@@ -594,6 +702,7 @@ const main = async () => {
                 `);
             }
 
+            // Check if comment is going above a method definition
             if ( def.type === 'method' ) {
                 instruction = dedent(`
                     Since the comment is going above a method, please write a JSDoc style comment.
@@ -602,6 +711,7 @@ const main = async () => {
                 `);
             }
             
+            // Check if comment is for a constant definition and set appropriate instruction
             if ( def.type === 'const' ) {
                 instruction = dedent(`
                     Since the comment is going above a constant definition, please write a comment that explains
@@ -619,6 +729,7 @@ const main = async () => {
         const driver_params = metadata['ai-params'] ??
             models_to_try[Math.floor(Math.random() * models_to_try.length)];
         
+        // Iterate through each comment object to add comments to the code
         for ( const comment of comments ) {
             // This doesn't work very well yet
             /*
@@ -642,12 +753,14 @@ const main = async () => {
                 ]
             });
             
+            // Check if the comment lines start with '/*' and ensure there's a blank line above it
             if ( ranges_message.content.trim() !== 'none' ) {
                 const ranges = ranges_message.content.split(',').map(range => {
                     const [ start, end ] = range.split('-').map(n => Number(n));
                     return { start, end };
                 });
                 
+                // Iterate through ranges and add key places for each range
                 for ( const range of ranges ) {
                     key_places.push({
                         anchor: range.start,
@@ -690,19 +803,23 @@ const main = async () => {
             comment.lines = ai_message_to_lines(message.content);
             
             // Remove leading and trailing blank lines
+            // Remove leading and trailing blank lines from comment lines array
             while ( comment.lines.length && ! comment.lines[0].trim() ) {
                 comment.lines.shift();
             }
+            // Remove trailing blank lines from comment lines array
             while ( comment.lines.length && ! comment.lines[comment.lines.length - 1].trim() ) {
                 comment.lines.pop();
             }
             
+            // Remove leading "```" or "```<language>" lines
             // Remove leading "```" or "```<language>" lines
             if ( comment.lines[0].startsWith('```') ) {
                 comment.lines.shift();
             }
             
             // Remove trailing "```" lines
+            // Remove trailing "```" lines if present
             if ( comment.lines[comment.lines.length - 1].startsWith('```') ) {
                 comment.lines.pop();
             }
@@ -715,6 +832,7 @@ const main = async () => {
         console.log('--- lines ---');
         console.log(lines);
         
+        // Check if file has metadata line and remove it before adding new metadata
         if ( has_metadata_line ) {
             lines.shift();
         }