Refactor symbol definition and reference retrieval

- Improved error handling and validation in symbol definition and reference retrieval functions.
- Enhanced the logic for finding the smallest symbol definition that contains the target range.
- Updated the function descriptions to provide more detailed information about their purpose, input, output, and error handling.
- Added 'ref_line' to the output of the symbol reference retrieval function.
This commit is contained in:
bobo.yang 2023-07-27 12:48:18 +08:00
parent 63fa9b1bd2
commit cd8f99405a
2 changed files with 247 additions and 180 deletions

View File

@ -18,132 +18,162 @@ const readFile = util.promisify(fs.readFile);
async function findSymbolInWorkspace(symbolName: string, symbolline: number, symbolFile: string): Promise<string[]> { async function findSymbolInWorkspace(symbolName: string, symbolline: number, symbolFile: string): Promise<string[]> {
const symbolPosition = await getSymbolPosition(symbolName, symbolline, symbolFile); const symbolPosition = await getSymbolPosition(symbolName, symbolline, symbolFile);
if (!symbolPosition) { if (!symbolPosition) {
return []; return [];
} }
// get definition of symbol // get definition of symbol
const refLocations = await vscode.commands.executeCommand<vscode.LocationLink[]>( const refLocations = await vscode.commands.executeCommand<vscode.Location[] | vscode.LocationLink[]>(
'vscode.executeDefinitionProvider', 'vscode.executeDefinitionProvider',
vscode.Uri.file(symbolFile), vscode.Uri.file(symbolFile),
symbolPosition symbolPosition
); );
if (!refLocations) { if (!refLocations) {
return []; return [];
} }
// get related source lines // get related source lines
let contextList: Set<string> = new Set(); const contextListPromises = refLocations.map(async (refLocation) => {
for (const refLocation of refLocations) { let refLocationFile: string;
const refLocationFile = refLocation.targetUri.fsPath; let targetRange: vscode.Range;
const documentNew = await vscode.workspace.openTextDocument(refLocationFile);
const data = { if (refLocation instanceof vscode.Location) {
path: refLocation.targetUri.fsPath, refLocationFile = refLocation.uri.fsPath;
start_line: refLocation.targetRange.start.line, targetRange = refLocation.range;
end_line: refLocation.targetRange.end.line,
content: documentNew.getText(refLocation.targetRange)
};
contextList.add(JSON.stringify(data));
// // get symbol define in symbolFile // get symbols in file
// const symbolsT: vscode.DocumentSymbol[] = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>( const symbols = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>(
// 'vscode.executeDocumentSymbolProvider', 'vscode.executeDocumentSymbolProvider',
// refLocation.targetUri vscode.Uri.file(refLocationFile)
// ); );
// if (!symbolsT) {
// continue;
// }
// let symbolsList: vscode.DocumentSymbol[] = [];
// const visitSymbol = (symbol: vscode.DocumentSymbol) => {
// symbolsList.push(symbol);
// if (symbol.children) {
// for (const child of symbol.children) {
// visitSymbol(child);
// }
// }
// };
// for (const symbol of symbolsT) {
// visitSymbol(symbol);
// }
// let symbol: vscode.DocumentSymbol | undefined = undefined; // find the smallest symbol definition that contains the target range
// for (const symbolT of symbolsList.reverse()) { let smallestSymbol: vscode.DocumentSymbol | undefined;
// if (symbolT.range.contains(refLocation.) && symbolT.name === symbolName) { for (const symbol of symbols) {
// symbol = symbolT; smallestSymbol = findSmallestSymbol(symbol, targetRange, smallestSymbol);
// break; }
// }
// }
// if (symbol) {
// const data = {
// path: refLocation.uri,
// start_line: symbol.range.start.line,
// content: documentNew.getText(symbol.range)
// };
// contextList.add(JSON.stringify(data));
// }
}
return Array.from(contextList); if (smallestSymbol) {
targetRange = smallestSymbol.range;
}
} else {
refLocationFile = refLocation.targetUri.fsPath;
targetRange = refLocation.targetRange;
}
const documentNew = await vscode.workspace.openTextDocument(refLocationFile);
const data = {
path: refLocationFile,
start_line: targetRange.start.line,
end_line: targetRange.end.line,
content: documentNew.getText(targetRange)
};
return JSON.stringify(data);
});
const contextList = await Promise.all(contextListPromises);
return contextList;
} }
function findSmallestSymbol(symbol: vscode.DocumentSymbol, targetRange: vscode.Range, smallestSymbol: vscode.DocumentSymbol | undefined): vscode.DocumentSymbol | undefined {
if (symbol.range.contains(targetRange) &&
(!smallestSymbol || smallestSymbol.range.contains(symbol.range))) {
smallestSymbol = symbol;
}
for (const child of symbol.children) {
smallestSymbol = findSmallestSymbol(child, targetRange, smallestSymbol);
}
return smallestSymbol;
}
export class SymbolDefAction implements Action { export class SymbolDefAction implements Action {
name: string; name: string;
description: string; description: string;
type: string[]; type: string[];
action: string; action: string;
handler: string[]; handler: string[];
args: { "name": string, "description": string, "type": string, "as"?: string, "required": boolean, "from": string }[]; args: { "name": string, "description": string, "type": string, "as"?: string, "required": boolean, "from": string }[];
constructor() { constructor() {
this.name = 'symbol_def'; this.name = 'symbol_def';
this.description = 'Retrieve the definition of symbol.'; this.description = `
this.type = ['symbol']; Function Purpose: This function retrieves the definition information for a given symbol.
this.action = 'symbol_def'; Input: The symbol should not be in string format or in 'a.b' format. To find the definition of 'a.b', simply refer to 'b'.
this.handler = []; Output: The function returns a dictionary with the following keys:
this.args = [ 'exitCode': If 'exitCode' is 0, the function execution was successful. If 'exitCode' is not 0, the function execution failed.
{ 'stdout': If the function executes successfully, 'stdout' is a list of JSON strings. Each JSON string contains:
"name": "symbol", 'path': The file path.
"description": "The symbol variable specifies the symbol for which definition information is to be retrieved.", 'start_line': The start line of the content.
"type": "string", 'end_line': The end line of the content.
"required": true, 'content': The source code related to the definition.
"from": "content.content.symbol" 'stderr': Error output if any.
}, { Error Handling: If the function execution fails, 'exitCode' will not be 0 and 'stderr' will contain the error information.`;
"name": "line", this.type = ['symbol'];
"description": 'The line variable specifies the line number of the symbol for which definition information is to be retrieved.', this.action = 'symbol_def';
"type": "number", this.handler = [];
"required": true, this.args = [
"from": "content.content.line" {
}, { "name": "symbol",
"name": "file", "description": "The symbol variable specifies the symbol for which definition information is to be retrieved.",
"description": 'File contain that symbol.', "type": "string",
"type": "string", "required": true,
"required": true, "from": "content.content.symbol"
"from": "content.content.file" }, {
} "name": "line",
]; "description": 'The line variable specifies the line number of the symbol for which definition information is to be retrieved.',
} "type": "number",
"required": true,
"from": "content.content.line"
}, {
"name": "file",
"description": 'File contain that symbol.',
"type": "string",
"required": true,
"from": "content.content.file"
}
];
}
async handlerAction(args: {[key: string]: any}): Promise<CommandResult> { async handlerAction(args: {[key: string]: any}): Promise<CommandResult> {
try { try {
const symbolName = args.symbol; const symbolName = args.symbol;
const symbolLine = args.line; const symbolLine = args.line;
let symbolFile = args.file; let symbolFile = args.file;
// if symbolFile is not absolute path, then get it's absolute path // Check if the symbol name is valid
if (!path.isAbsolute(symbolFile)) { if (!symbolName || typeof symbolName !== 'string') {
const basePath = UiUtilWrapper.workspaceFoldersFirstPath(); throw new Error('Invalid symbol name. It should be a non-empty string.');
symbolFile = path.join(basePath!, symbolFile); }
}
// get reference information // Check if the symbol line is valid
const refList = await findSymbolInWorkspace(symbolName, symbolLine, symbolFile); if (!symbolLine || typeof symbolLine !== 'number' || symbolLine < 0) {
throw new Error('Invalid symbol line. It should be a non-negative number.');
}
return {exitCode: 0, stdout: JSON.stringify(refList), stderr: ""}; // Check if the symbol file is valid
} catch (error) { if (!symbolFile || typeof symbolFile !== 'string') {
logger.channel()?.error(`${this.name} handle error: ${error}`); throw new Error('Invalid symbol file. It should be a non-empty string.');
logger.channel()?.show(); }
return {exitCode: -1, stdout: '', stderr: `${this.name} handle error: ${error}`};
} // if symbolFile is not absolute path, then get it's absolute path
} if (!path.isAbsolute(symbolFile)) {
const basePath = UiUtilWrapper.workspaceFoldersFirstPath();
symbolFile = path.join(basePath!, symbolFile);
}
// get reference information
const refList = await findSymbolInWorkspace(symbolName, symbolLine, symbolFile);
return {exitCode: 0, stdout: JSON.stringify(refList), stderr: ""};
} catch (error) {
logger.channel()?.error(`${this.name} handle error: ${error}`);
logger.channel()?.show();
return {exitCode: -1, stdout: '', stderr: `${this.name} handle error: ${error}`};
}
}
}; };

View File

@ -16,39 +16,48 @@ const readFile = util.promisify(fs.readFile);
async function isCorrectIndexSymbol(filename: string, position: vscode.Position, symbolName: string): Promise< boolean > { async function isCorrectIndexSymbol(filename: string, position: vscode.Position, symbolName: string): Promise< boolean > {
const defLocations = await vscode.commands.executeCommand<any[]>( let defLocations = await vscode.commands.executeCommand<any[]>(
'vscode.executeDefinitionProvider', 'vscode.executeDefinitionProvider',
vscode.Uri.file(filename), vscode.Uri.file(filename),
position position
); );
if (!defLocations) {
return false;
}
for (const defLocation of defLocations) { if (!defLocations || defLocations.length === 0) {
let range = undefined; defLocations = await vscode.commands.executeCommand<any[]>(
let uri = undefined; 'vscode.executeReferenceProvider',
if (defLocation.targetSelectionRange) { vscode.Uri.file(filename),
range = defLocation.targetSelectionRange; position
uri = defLocation.targetUri; );
} else if (defLocation.targetRange) { }
range = defLocation.targetRange;
uri = defLocation.targetUri;
} else {
range = defLocation.range;
uri = defLocation.uri;
}
if (!range) {
continue;
}
const documentNew = await vscode.workspace.openTextDocument(uri); if (!defLocations) {
const sbName = await documentNew.getText(range); return false;
if (sbName === symbolName) { }
return true;
} for (const defLocation of defLocations) {
} let range = undefined;
return false; let uri = undefined;
if (defLocation.targetSelectionRange) {
range = defLocation.targetSelectionRange;
uri = defLocation.targetUri;
} else if (defLocation.targetRange) {
range = defLocation.targetRange;
uri = defLocation.targetUri;
} else {
range = defLocation.range;
uri = defLocation.uri;
}
if (!range) {
continue;
}
const documentNew = await vscode.workspace.openTextDocument(uri);
const sbName = await documentNew.getText(range);
if (sbName === symbolName) {
return true;
}
}
return false;
} }
export async function getSymbolPosition(symbolName: string, symbolLine: number, symbolFile: string): Promise<vscode.Position | undefined> { export async function getSymbolPosition(symbolName: string, symbolLine: number, symbolFile: string): Promise<vscode.Position | undefined> {
@ -89,8 +98,10 @@ export async function getSymbolPosition(symbolName: string, symbolLine: number,
async function findSymbolInWorkspace(symbolName: string, symbolline: number, symbolFile: string): Promise<string[]> { async function findSymbolInWorkspace(symbolName: string, symbolline: number, symbolFile: string): Promise<string[]> {
const symbolPosition = await getSymbolPosition(symbolName, symbolline, symbolFile); const symbolPosition = await getSymbolPosition(symbolName, symbolline, symbolFile);
if (!symbolPosition) { if (!symbolPosition) {
return []; throw new Error(`Symbol "${symbolName}" not found in file "${symbolFile}" at line ${symbolline}.`);
} }
logger.channel()?.info(`symbol position: ${symbolPosition.line}:${symbolPosition.character}`);
// get all references of symbol // get all references of symbol
const refLocations = await vscode.commands.executeCommand<vscode.Location[]>( const refLocations = await vscode.commands.executeCommand<vscode.Location[]>(
@ -98,9 +109,9 @@ async function findSymbolInWorkspace(symbolName: string, symbolline: number, sym
vscode.Uri.file(symbolFile), vscode.Uri.file(symbolFile),
symbolPosition symbolPosition
); );
if (!refLocations) { if (!refLocations || refLocations.length === 0) {
return []; throw new Error(`No references found for symbol "${symbolName}" in file "${symbolFile}" at line ${symbolline}.`);
} }
// get related source lines // get related source lines
let contextList: Set<string> = new Set(); let contextList: Set<string> = new Set();
@ -118,10 +129,6 @@ async function findSymbolInWorkspace(symbolName: string, symbolline: number, sym
'vscode.executeDocumentSymbolProvider', 'vscode.executeDocumentSymbolProvider',
refLocation.uri refLocation.uri
); );
if (!symbolsT) {
logger.channel()?.info(`Symbol ref continue...`);
continue;
}
let symbolsList: vscode.DocumentSymbol[] = []; let symbolsList: vscode.DocumentSymbol[] = [];
const visitSymbol = (symbol: vscode.DocumentSymbol) => { const visitSymbol = (symbol: vscode.DocumentSymbol) => {
symbolsList.push(symbol); symbolsList.push(symbol);
@ -131,8 +138,10 @@ async function findSymbolInWorkspace(symbolName: string, symbolline: number, sym
} }
} }
}; };
for (const symbol of symbolsT) { if (symbolsT) {
visitSymbol(symbol); for (const symbol of symbolsT) {
visitSymbol(symbol);
}
} }
let symbol: vscode.DocumentSymbol | undefined = undefined; let symbol: vscode.DocumentSymbol | undefined = undefined;
@ -149,6 +158,7 @@ async function findSymbolInWorkspace(symbolName: string, symbolline: number, sym
const data = { const data = {
path: refLocationFile, path: refLocationFile,
start_line: startLine, start_line: startLine,
ref_line: refLocation.range.start.line,
content: documentNew.getText(rangeNew), content: documentNew.getText(rangeNew),
parentDefine: symbolName, parentDefine: symbolName,
parentDefineStartLine: symbolLine parentDefineStartLine: symbolLine
@ -168,7 +178,19 @@ export class SymbolRefAction implements Action {
constructor() { constructor() {
this.name = 'symbol_ref'; this.name = 'symbol_ref';
this.description = 'Retrieve the reference information related to the symbol'; this.description = `
Function Purpose: This function retrieves the reference information for a given symbol.
Input: The symbol should not be in string format or in 'a.b' format. To find the reference of 'a.b', simply refer to 'b'.
Output: The function returns a dictionary with the following keys:
'exitCode': If 'exitCode' is 0, the function execution was successful. If 'exitCode' is not 0, the function execution failed.
'stdout': If the function executes successfully, 'stdout' is a list of JSON strings. Each JSON string contains:
'path': The file path.
'ref_line': The ref line of the symbol.
'content': The source code related to the reference.
'parentDefine': The parent symbol name of the reference, which is always a function name or class name.
'parentDefineStartLine': The start line of the parent symbol name of the reference.
'stderr': Error output if any.
Error Handling: If the function execution fails, 'exitCode' will not be 0 and 'stderr' will contain the error information.`;
this.type = ['symbol']; this.type = ['symbol'];
this.action = 'symbol_ref'; this.action = 'symbol_ref';
this.handler = []; this.handler = [];
@ -196,25 +218,40 @@ export class SymbolRefAction implements Action {
} }
async handlerAction(args: {[key: string]: any}): Promise<CommandResult> { async handlerAction(args: {[key: string]: any}): Promise<CommandResult> {
try { try {
const symbolName = args.symbol; const symbolName = args.symbol;
const symbolLine = args.line; const symbolLine = args.line;
let symbolFile = args.file; let symbolFile = args.file;
// if symbolFile is not absolute path, then get it's absolute path // Check if the symbol name is valid
if (!path.isAbsolute(symbolFile)) { if (!symbolName || typeof symbolName !== 'string') {
const basePath = UiUtilWrapper.workspaceFoldersFirstPath(); throw new Error('Invalid symbol name. It should be a non-empty string.');
symbolFile = path.join(basePath!, symbolFile); }
}
// get reference information // Check if the symbol line is valid
const refList = await findSymbolInWorkspace(symbolName, symbolLine, symbolFile); if (!symbolLine || typeof symbolLine !== 'number' || symbolLine < 0) {
throw new Error('Invalid symbol line. It should be a non-negative number.');
}
return {exitCode: 0, stdout: JSON.stringify(refList), stderr: ""}; // Check if the symbol file is valid
} catch (error) { if (!symbolFile || typeof symbolFile !== 'string') {
logger.channel()?.error(`${this.name} handle error: ${error}`); throw new Error('Invalid symbol file. It should be a non-empty string.');
logger.channel()?.show(); }
return {exitCode: -1, stdout: '', stderr: `${this.name} handle error: ${error}`};
} // if symbolFile is not absolute path, then get it's absolute path
} if (!path.isAbsolute(symbolFile)) {
const basePath = UiUtilWrapper.workspaceFoldersFirstPath();
symbolFile = path.join(basePath!, symbolFile);
}
// get reference information
const refList = await findSymbolInWorkspace(symbolName, symbolLine, symbolFile);
return {exitCode: 0, stdout: JSON.stringify(refList), stderr: ""};
} catch (error) {
logger.channel()?.error(`${this.name} handle error: ${error}`);
logger.channel()?.show();
return {exitCode: -1, stdout: '', stderr: `${this.name} handle error: ${error}`};
}
}
}; };