🔥 Remove powershell prettier plugin
This commit is contained in:
@@ -1,391 +0,0 @@
|
|||||||
/**
|
|
||||||
* PowerShell AST 节点定义
|
|
||||||
* 定义抽象语法树的各种节点类型
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Token } from './lexer';
|
|
||||||
|
|
||||||
export interface ASTNode {
|
|
||||||
type: string;
|
|
||||||
start: number;
|
|
||||||
end: number;
|
|
||||||
line: number;
|
|
||||||
column: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScriptBlockAst extends ASTNode {
|
|
||||||
type: 'ScriptBlock';
|
|
||||||
statements: StatementAst[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StatementAst extends ASTNode {
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExpressionAst extends ASTNode {
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 管道表达式
|
|
||||||
export interface PipelineAst extends StatementAst {
|
|
||||||
type: 'Pipeline';
|
|
||||||
elements: PipelineElementAst[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PipelineElementAst extends ASTNode {
|
|
||||||
type: 'PipelineElement';
|
|
||||||
expression: ExpressionAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 命令表达式
|
|
||||||
export interface CommandAst extends ExpressionAst {
|
|
||||||
type: 'Command';
|
|
||||||
commandName: string;
|
|
||||||
parameters: ParameterAst[];
|
|
||||||
arguments: ExpressionAst[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ParameterAst extends ASTNode {
|
|
||||||
type: 'Parameter';
|
|
||||||
name: string;
|
|
||||||
value?: ExpressionAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 赋值表达式
|
|
||||||
export interface AssignmentAst extends StatementAst {
|
|
||||||
type: 'Assignment';
|
|
||||||
left: ExpressionAst;
|
|
||||||
operator: string;
|
|
||||||
right: ExpressionAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 变量表达式
|
|
||||||
export interface VariableAst extends ExpressionAst {
|
|
||||||
type: 'Variable';
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 字面量表达式
|
|
||||||
export interface LiteralAst extends ExpressionAst {
|
|
||||||
type: 'Literal';
|
|
||||||
value: any;
|
|
||||||
literalType: 'String' | 'Number' | 'Boolean' | 'Null';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数组表达式
|
|
||||||
export interface ArrayAst extends ExpressionAst {
|
|
||||||
type: 'Array';
|
|
||||||
elements: ExpressionAst[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 哈希表表达式
|
|
||||||
export interface HashtableAst extends ExpressionAst {
|
|
||||||
type: 'Hashtable';
|
|
||||||
entries: HashtableEntryAst[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HashtableEntryAst extends ASTNode {
|
|
||||||
type: 'HashtableEntry';
|
|
||||||
key: ExpressionAst;
|
|
||||||
value: ExpressionAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 函数定义
|
|
||||||
export interface FunctionDefinitionAst extends StatementAst {
|
|
||||||
type: 'FunctionDefinition';
|
|
||||||
name: string;
|
|
||||||
parameters: ParameterAst[];
|
|
||||||
body: ScriptBlockAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 控制流结构
|
|
||||||
export interface IfStatementAst extends StatementAst {
|
|
||||||
type: 'IfStatement';
|
|
||||||
condition: ExpressionAst;
|
|
||||||
ifBody: ScriptBlockAst;
|
|
||||||
elseIfClauses: ElseIfClauseAst[];
|
|
||||||
elseBody?: ScriptBlockAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ElseIfClauseAst extends ASTNode {
|
|
||||||
type: 'ElseIfClause';
|
|
||||||
condition: ExpressionAst;
|
|
||||||
body: ScriptBlockAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WhileStatementAst extends StatementAst {
|
|
||||||
type: 'WhileStatement';
|
|
||||||
condition: ExpressionAst;
|
|
||||||
body: ScriptBlockAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ForStatementAst extends StatementAst {
|
|
||||||
type: 'ForStatement';
|
|
||||||
initializer?: ExpressionAst;
|
|
||||||
condition?: ExpressionAst;
|
|
||||||
iterator?: ExpressionAst;
|
|
||||||
body: ScriptBlockAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ForEachStatementAst extends StatementAst {
|
|
||||||
type: 'ForEachStatement';
|
|
||||||
variable: VariableAst;
|
|
||||||
iterable: ExpressionAst;
|
|
||||||
body: ScriptBlockAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwitchStatementAst extends StatementAst {
|
|
||||||
type: 'SwitchStatement';
|
|
||||||
value: ExpressionAst;
|
|
||||||
clauses: SwitchClauseAst[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SwitchClauseAst extends ASTNode {
|
|
||||||
type: 'SwitchClause';
|
|
||||||
pattern: ExpressionAst;
|
|
||||||
body: ScriptBlockAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TryStatementAst extends StatementAst {
|
|
||||||
type: 'TryStatement';
|
|
||||||
body: ScriptBlockAst;
|
|
||||||
catchClauses: CatchClauseAst[];
|
|
||||||
finallyClause?: FinallyClauseAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CatchClauseAst extends ASTNode {
|
|
||||||
type: 'CatchClause';
|
|
||||||
exceptionType?: string;
|
|
||||||
body: ScriptBlockAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FinallyClauseAst extends ASTNode {
|
|
||||||
type: 'FinallyClause';
|
|
||||||
body: ScriptBlockAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 二元操作表达式
|
|
||||||
export interface BinaryExpressionAst extends ExpressionAst {
|
|
||||||
type: 'BinaryExpression';
|
|
||||||
left: ExpressionAst;
|
|
||||||
operator: string;
|
|
||||||
right: ExpressionAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 一元操作表达式
|
|
||||||
export interface UnaryExpressionAst extends ExpressionAst {
|
|
||||||
type: 'UnaryExpression';
|
|
||||||
operator: string;
|
|
||||||
operand: ExpressionAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 括号表达式
|
|
||||||
export interface ParenthesizedExpressionAst extends ExpressionAst {
|
|
||||||
type: 'ParenthesizedExpression';
|
|
||||||
expression: ExpressionAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法调用表达式
|
|
||||||
export interface MethodCallAst extends ExpressionAst {
|
|
||||||
type: 'MethodCall';
|
|
||||||
object: ExpressionAst;
|
|
||||||
methodName: string;
|
|
||||||
arguments: ExpressionAst[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// 属性访问表达式
|
|
||||||
export interface PropertyAccessAst extends ExpressionAst {
|
|
||||||
type: 'PropertyAccess';
|
|
||||||
object: ExpressionAst;
|
|
||||||
propertyName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 索引访问表达式
|
|
||||||
export interface IndexAccessAst extends ExpressionAst {
|
|
||||||
type: 'IndexAccess';
|
|
||||||
object: ExpressionAst;
|
|
||||||
index: ExpressionAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 注释节点
|
|
||||||
export interface CommentAst extends ASTNode {
|
|
||||||
type: 'Comment';
|
|
||||||
text: string;
|
|
||||||
isMultiline: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 空白节点
|
|
||||||
export interface WhitespaceAst extends ASTNode {
|
|
||||||
type: 'Whitespace';
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 工厂函数,用于创建AST节点
|
|
||||||
export class ASTNodeFactory {
|
|
||||||
static createScriptBlock(statements: StatementAst[], start: number, end: number, line: number, column: number): ScriptBlockAst {
|
|
||||||
return {
|
|
||||||
type: 'ScriptBlock',
|
|
||||||
statements,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createPipeline(elements: PipelineElementAst[], start: number, end: number, line: number, column: number): PipelineAst {
|
|
||||||
return {
|
|
||||||
type: 'Pipeline',
|
|
||||||
elements,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createCommand(commandName: string, parameters: ParameterAst[], args: ExpressionAst[], start: number, end: number, line: number, column: number): CommandAst {
|
|
||||||
return {
|
|
||||||
type: 'Command',
|
|
||||||
commandName,
|
|
||||||
parameters,
|
|
||||||
arguments: args,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createAssignment(left: ExpressionAst, operator: string, right: ExpressionAst, start: number, end: number, line: number, column: number): AssignmentAst {
|
|
||||||
return {
|
|
||||||
type: 'Assignment',
|
|
||||||
left,
|
|
||||||
operator,
|
|
||||||
right,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createVariable(name: string, start: number, end: number, line: number, column: number): VariableAst {
|
|
||||||
return {
|
|
||||||
type: 'Variable',
|
|
||||||
name,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createLiteral(value: any, literalType: 'String' | 'Number' | 'Boolean' | 'Null', start: number, end: number, line: number, column: number): LiteralAst {
|
|
||||||
return {
|
|
||||||
type: 'Literal',
|
|
||||||
value,
|
|
||||||
literalType,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createBinaryExpression(left: ExpressionAst, operator: string, right: ExpressionAst, start: number, end: number, line: number, column: number): BinaryExpressionAst {
|
|
||||||
return {
|
|
||||||
type: 'BinaryExpression',
|
|
||||||
left,
|
|
||||||
operator,
|
|
||||||
right,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createIfStatement(condition: ExpressionAst, ifBody: ScriptBlockAst, elseIfClauses: ElseIfClauseAst[], elseBody: ScriptBlockAst | undefined, start: number, end: number, line: number, column: number): IfStatementAst {
|
|
||||||
return {
|
|
||||||
type: 'IfStatement',
|
|
||||||
condition,
|
|
||||||
ifBody,
|
|
||||||
elseIfClauses,
|
|
||||||
elseBody,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createFunctionDefinition(name: string, parameters: ParameterAst[], body: ScriptBlockAst, start: number, end: number, line: number, column: number): FunctionDefinitionAst {
|
|
||||||
return {
|
|
||||||
type: 'FunctionDefinition',
|
|
||||||
name,
|
|
||||||
parameters,
|
|
||||||
body,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createComment(text: string, isMultiline: boolean, start: number, end: number, line: number, column: number): CommentAst {
|
|
||||||
return {
|
|
||||||
type: 'Comment',
|
|
||||||
text,
|
|
||||||
isMultiline,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AST访问者模式接口
|
|
||||||
export interface ASTVisitor<T> {
|
|
||||||
visitScriptBlock(node: ScriptBlockAst): T;
|
|
||||||
visitPipeline(node: PipelineAst): T;
|
|
||||||
visitCommand(node: CommandAst): T;
|
|
||||||
visitAssignment(node: AssignmentAst): T;
|
|
||||||
visitVariable(node: VariableAst): T;
|
|
||||||
visitLiteral(node: LiteralAst): T;
|
|
||||||
visitBinaryExpression(node: BinaryExpressionAst): T;
|
|
||||||
visitIfStatement(node: IfStatementAst): T;
|
|
||||||
visitFunctionDefinition(node: FunctionDefinitionAst): T;
|
|
||||||
visitComment(node: CommentAst): T;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AST遍历工具类
|
|
||||||
export class ASTTraverser {
|
|
||||||
static traverse<T>(node: ASTNode, visitor: Partial<ASTVisitor<T>>): T | undefined {
|
|
||||||
switch (node.type) {
|
|
||||||
case 'ScriptBlock':
|
|
||||||
return visitor.visitScriptBlock?.(node as ScriptBlockAst);
|
|
||||||
case 'Pipeline':
|
|
||||||
return visitor.visitPipeline?.(node as PipelineAst);
|
|
||||||
case 'Command':
|
|
||||||
return visitor.visitCommand?.(node as CommandAst);
|
|
||||||
case 'Assignment':
|
|
||||||
return visitor.visitAssignment?.(node as AssignmentAst);
|
|
||||||
case 'Variable':
|
|
||||||
return visitor.visitVariable?.(node as VariableAst);
|
|
||||||
case 'Literal':
|
|
||||||
return visitor.visitLiteral?.(node as LiteralAst);
|
|
||||||
case 'BinaryExpression':
|
|
||||||
return visitor.visitBinaryExpression?.(node as BinaryExpressionAst);
|
|
||||||
case 'IfStatement':
|
|
||||||
return visitor.visitIfStatement?.(node as IfStatementAst);
|
|
||||||
case 'FunctionDefinition':
|
|
||||||
return visitor.visitFunctionDefinition?.(node as FunctionDefinitionAst);
|
|
||||||
case 'Comment':
|
|
||||||
return visitor.visitComment?.(node as CommentAst);
|
|
||||||
default:
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,566 +0,0 @@
|
|||||||
/**
|
|
||||||
* PowerShell 代码生成器
|
|
||||||
* 遍历AST并根据格式化规则生成格式化的PowerShell代码
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
ASTNode,
|
|
||||||
ScriptBlockAst,
|
|
||||||
StatementAst,
|
|
||||||
ExpressionAst,
|
|
||||||
PipelineAst,
|
|
||||||
CommandAst,
|
|
||||||
AssignmentAst,
|
|
||||||
VariableAst,
|
|
||||||
LiteralAst,
|
|
||||||
BinaryExpressionAst,
|
|
||||||
IfStatementAst,
|
|
||||||
FunctionDefinitionAst,
|
|
||||||
ParameterAst,
|
|
||||||
CommentAst,
|
|
||||||
PipelineElementAst,
|
|
||||||
ElseIfClauseAst,
|
|
||||||
ASTTraverser
|
|
||||||
} from './ast';
|
|
||||||
import { FormatterRules, FormatterOptions } from './formatter-rules';
|
|
||||||
|
|
||||||
export class PowerShellCodeGenerator {
|
|
||||||
private rules: FormatterRules;
|
|
||||||
private indentLevel: number = 0;
|
|
||||||
private output: string[] = [];
|
|
||||||
private currentLineLength: number = 0;
|
|
||||||
private needsNewline: boolean = false;
|
|
||||||
private lastWasComment: boolean = false;
|
|
||||||
|
|
||||||
constructor(options: Partial<FormatterOptions> = {}) {
|
|
||||||
this.rules = new FormatterRules(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成格式化的PowerShell代码
|
|
||||||
*/
|
|
||||||
public generate(ast: ScriptBlockAst, comments: CommentAst[] = []): string {
|
|
||||||
this.output = [];
|
|
||||||
this.indentLevel = 0;
|
|
||||||
this.currentLineLength = 0;
|
|
||||||
this.needsNewline = false;
|
|
||||||
this.lastWasComment = false;
|
|
||||||
|
|
||||||
// 首先处理文档开头的注释
|
|
||||||
this.generateLeadingComments(comments);
|
|
||||||
|
|
||||||
// 生成主体代码
|
|
||||||
this.generateScriptBlock(ast);
|
|
||||||
|
|
||||||
// 处理文档末尾
|
|
||||||
this.handleFinalNewline();
|
|
||||||
|
|
||||||
const result = this.output.join('');
|
|
||||||
return this.postProcess(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateScriptBlock(node: ScriptBlockAst): void {
|
|
||||||
for (let i = 0; i < node.statements.length; i++) {
|
|
||||||
const statement = node.statements[i];
|
|
||||||
const nextStatement = i < node.statements.length - 1 ? node.statements[i + 1] : null;
|
|
||||||
|
|
||||||
this.generateStatement(statement);
|
|
||||||
|
|
||||||
// 在语句之间添加适当的空行
|
|
||||||
if (nextStatement) {
|
|
||||||
this.addStatementSeparation(statement, nextStatement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateStatement(statement: StatementAst): void {
|
|
||||||
switch (statement.type) {
|
|
||||||
case 'Pipeline':
|
|
||||||
this.generatePipeline(statement as PipelineAst);
|
|
||||||
break;
|
|
||||||
case 'Assignment':
|
|
||||||
this.generateAssignment(statement as AssignmentAst);
|
|
||||||
break;
|
|
||||||
case 'IfStatement':
|
|
||||||
this.generateIfStatement(statement as IfStatementAst);
|
|
||||||
break;
|
|
||||||
case 'FunctionDefinition':
|
|
||||||
this.generateFunctionDefinition(statement as FunctionDefinitionAst);
|
|
||||||
break;
|
|
||||||
case 'RawText':
|
|
||||||
// 处理解析失败时的原始文本
|
|
||||||
this.append((statement as any).value);
|
|
||||||
return; // 不需要添加额外的换行
|
|
||||||
default:
|
|
||||||
this.append(`/* Unsupported statement type: ${statement.type} */`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ensureNewline();
|
|
||||||
}
|
|
||||||
|
|
||||||
private generatePipeline(pipeline: PipelineAst): void {
|
|
||||||
if (!this.rules.formatPipelines) {
|
|
||||||
// 简单连接所有元素
|
|
||||||
for (let i = 0; i < pipeline.elements.length; i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
this.append(' | ');
|
|
||||||
}
|
|
||||||
this.generatePipelineElement(pipeline.elements[i]);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const style = this.rules.getPipelineStyle(pipeline.elements.length);
|
|
||||||
|
|
||||||
if (style === 'multiline') {
|
|
||||||
this.generateMultilinePipeline(pipeline);
|
|
||||||
} else {
|
|
||||||
this.generateOnelinePipeline(pipeline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateOnelinePipeline(pipeline: PipelineAst): void {
|
|
||||||
for (let i = 0; i < pipeline.elements.length; i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
this.append(' | ');
|
|
||||||
}
|
|
||||||
this.generatePipelineElement(pipeline.elements[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateMultilinePipeline(pipeline: PipelineAst): void {
|
|
||||||
for (let i = 0; i < pipeline.elements.length; i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
this.appendLine(' |');
|
|
||||||
this.appendIndent();
|
|
||||||
}
|
|
||||||
this.generatePipelineElement(pipeline.elements[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generatePipelineElement(element: PipelineElementAst): void {
|
|
||||||
this.generateExpression(element.expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateExpression(expression: ExpressionAst): void {
|
|
||||||
switch (expression.type) {
|
|
||||||
case 'Command':
|
|
||||||
this.generateCommand(expression as CommandAst);
|
|
||||||
break;
|
|
||||||
case 'Variable':
|
|
||||||
this.generateVariable(expression as VariableAst);
|
|
||||||
break;
|
|
||||||
case 'Literal':
|
|
||||||
this.generateLiteral(expression as LiteralAst);
|
|
||||||
break;
|
|
||||||
case 'BinaryExpression':
|
|
||||||
this.generateBinaryExpression(expression as BinaryExpressionAst);
|
|
||||||
break;
|
|
||||||
case 'ParenthesizedExpression':
|
|
||||||
this.append('(');
|
|
||||||
this.generateExpression((expression as any).expression);
|
|
||||||
this.append(')');
|
|
||||||
break;
|
|
||||||
case 'Array':
|
|
||||||
this.generateArray(expression as any);
|
|
||||||
break;
|
|
||||||
case 'Hashtable':
|
|
||||||
this.generateHashtable(expression as any);
|
|
||||||
break;
|
|
||||||
case 'ScriptBlockExpression':
|
|
||||||
this.generateScriptBlockExpression(expression as any);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.append(`/* Unsupported expression type: ${expression.type} */`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateCommand(command: CommandAst): void {
|
|
||||||
// 保持cmdlet名称的连字符,不进行破坏性的格式化
|
|
||||||
let commandName = command.commandName;
|
|
||||||
|
|
||||||
// 只有在明确指定要改变大小写时才进行格式化
|
|
||||||
// 但绝对不能删除连字符
|
|
||||||
if (this.rules.shouldFormatCommandCase()) {
|
|
||||||
commandName = this.rules.formatCommandCase(commandName);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.append(commandName);
|
|
||||||
|
|
||||||
// 生成参数
|
|
||||||
for (const param of command.parameters) {
|
|
||||||
this.append(' ');
|
|
||||||
this.generateParameter(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成位置参数
|
|
||||||
for (const arg of command.arguments) {
|
|
||||||
this.append(' ');
|
|
||||||
this.generateExpression(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateParameter(parameter: ParameterAst): void {
|
|
||||||
const paramName = this.rules.formatParameterCase(parameter.name);
|
|
||||||
this.append(paramName);
|
|
||||||
|
|
||||||
if (parameter.value) {
|
|
||||||
this.append(' ');
|
|
||||||
this.generateExpression(parameter.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateVariable(variable: VariableAst): void {
|
|
||||||
const formattedName = this.rules.formatVariableCase(variable.name);
|
|
||||||
this.append(formattedName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateLiteral(literal: LiteralAst): void {
|
|
||||||
if (literal.literalType === 'String') {
|
|
||||||
const formattedString = this.rules.formatQuotes(literal.value as string);
|
|
||||||
this.append(formattedString);
|
|
||||||
} else {
|
|
||||||
this.append(String(literal.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateBinaryExpression(expression: BinaryExpressionAst): void {
|
|
||||||
this.generateExpression(expression.left);
|
|
||||||
|
|
||||||
// 根据PowerShell官方规范,属性访问操作符绝对不能加空格
|
|
||||||
if (expression.operator === '.' ||
|
|
||||||
expression.operator === '::' ||
|
|
||||||
expression.operator === '[' ||
|
|
||||||
expression.operator === ']' ||
|
|
||||||
expression.operator === '@{') {
|
|
||||||
// 属性访问是PowerShell面向对象的核心,必须保持紧凑
|
|
||||||
this.append(expression.operator);
|
|
||||||
} else {
|
|
||||||
// 使用格式化规则处理其他操作符
|
|
||||||
const formattedOperator = this.rules.formatOperatorSpacing(expression.operator);
|
|
||||||
this.append(formattedOperator);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.generateExpression(expression.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateAssignment(assignment: AssignmentAst): void {
|
|
||||||
this.generateExpression(assignment.left);
|
|
||||||
|
|
||||||
const formattedOperator = this.rules.formatOperatorSpacing(assignment.operator);
|
|
||||||
this.append(formattedOperator);
|
|
||||||
|
|
||||||
this.generateExpression(assignment.right);
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateIfStatement(ifStmt: IfStatementAst): void {
|
|
||||||
// if 条件
|
|
||||||
this.append('if ');
|
|
||||||
this.append(this.rules.formatParentheses(''));
|
|
||||||
this.append('(');
|
|
||||||
this.generateExpression(ifStmt.condition);
|
|
||||||
this.append(')');
|
|
||||||
|
|
||||||
// if 主体
|
|
||||||
this.append(this.rules.getBraceStart());
|
|
||||||
this.appendLine('');
|
|
||||||
this.indent();
|
|
||||||
this.generateScriptBlock(ifStmt.ifBody);
|
|
||||||
this.outdent();
|
|
||||||
this.appendIndent();
|
|
||||||
this.append('}');
|
|
||||||
|
|
||||||
// elseif 子句
|
|
||||||
for (const elseIfClause of ifStmt.elseIfClauses) {
|
|
||||||
this.generateElseIfClause(elseIfClause);
|
|
||||||
}
|
|
||||||
|
|
||||||
// else 子句
|
|
||||||
if (ifStmt.elseBody) {
|
|
||||||
this.append(' else');
|
|
||||||
this.append(this.rules.getBraceStart());
|
|
||||||
this.appendLine('');
|
|
||||||
this.indent();
|
|
||||||
this.generateScriptBlock(ifStmt.elseBody);
|
|
||||||
this.outdent();
|
|
||||||
this.appendIndent();
|
|
||||||
this.append('}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateElseIfClause(elseIf: ElseIfClauseAst): void {
|
|
||||||
this.append(' elseif (');
|
|
||||||
this.generateExpression(elseIf.condition);
|
|
||||||
this.append(')');
|
|
||||||
this.append(this.rules.getBraceStart());
|
|
||||||
this.appendLine('');
|
|
||||||
this.indent();
|
|
||||||
this.generateScriptBlock(elseIf.body);
|
|
||||||
this.outdent();
|
|
||||||
this.appendIndent();
|
|
||||||
this.append('}');
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateFunctionDefinition(func: FunctionDefinitionAst): void {
|
|
||||||
// 函数前的空行
|
|
||||||
if (this.rules.blankLinesAroundFunctions > 0) {
|
|
||||||
for (let i = 0; i < this.rules.blankLinesAroundFunctions; i++) {
|
|
||||||
this.appendLine('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.append('function ');
|
|
||||||
this.append(func.name);
|
|
||||||
|
|
||||||
// 参数列表
|
|
||||||
if (func.parameters.length > 0) {
|
|
||||||
this.append('(');
|
|
||||||
for (let i = 0; i < func.parameters.length; i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
this.append(this.rules.formatComma());
|
|
||||||
}
|
|
||||||
this.generateParameter(func.parameters[i]);
|
|
||||||
}
|
|
||||||
this.append(')');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 函数体
|
|
||||||
this.append(this.rules.getBraceStart());
|
|
||||||
this.appendLine('');
|
|
||||||
this.indent();
|
|
||||||
this.generateScriptBlock(func.body);
|
|
||||||
this.outdent();
|
|
||||||
this.appendIndent();
|
|
||||||
this.append('}');
|
|
||||||
|
|
||||||
// 函数后的空行
|
|
||||||
if (this.rules.blankLinesAroundFunctions > 0) {
|
|
||||||
for (let i = 0; i < this.rules.blankLinesAroundFunctions; i++) {
|
|
||||||
this.appendLine('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateLeadingComments(comments: CommentAst[]): void {
|
|
||||||
const leadingComments = comments.filter(c => this.isLeadingComment(c));
|
|
||||||
for (const comment of leadingComments) {
|
|
||||||
this.generateComment(comment);
|
|
||||||
this.appendLine('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateComment(comment: CommentAst): void {
|
|
||||||
if (!this.rules.formatComments) {
|
|
||||||
this.append(comment.text);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comment.isMultiline) {
|
|
||||||
this.generateMultilineComment(comment.text);
|
|
||||||
} else {
|
|
||||||
this.generateSingleLineComment(comment.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastWasComment = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateArray(arrayExpr: any): void {
|
|
||||||
this.append('@(');
|
|
||||||
if (arrayExpr.elements && arrayExpr.elements.length > 0) {
|
|
||||||
for (let i = 0; i < arrayExpr.elements.length; i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
this.append(this.rules.formatComma());
|
|
||||||
}
|
|
||||||
this.generateExpression(arrayExpr.elements[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.append(')');
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateHashtable(hashtableExpr: any): void {
|
|
||||||
this.append('@{');
|
|
||||||
|
|
||||||
if (hashtableExpr.entries && hashtableExpr.entries.length > 0) {
|
|
||||||
// 强制使用紧凑格式,避免换行问题
|
|
||||||
for (let i = 0; i < hashtableExpr.entries.length; i++) {
|
|
||||||
const entry = hashtableExpr.entries[i];
|
|
||||||
|
|
||||||
this.generateExpression(entry.key);
|
|
||||||
this.append('=');
|
|
||||||
this.generateExpression(entry.value);
|
|
||||||
|
|
||||||
// 如果不是最后一个条目,添加分号和空格
|
|
||||||
if (i < hashtableExpr.entries.length - 1) {
|
|
||||||
this.append('; ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.append('}');
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateScriptBlockExpression(scriptBlockExpr: any): void {
|
|
||||||
this.append('{');
|
|
||||||
|
|
||||||
// 对原始内容应用基本的格式化规则
|
|
||||||
if (scriptBlockExpr.rawContent) {
|
|
||||||
const formattedContent = this.formatScriptBlockContent(scriptBlockExpr.rawContent);
|
|
||||||
this.append(formattedContent);
|
|
||||||
} else if (scriptBlockExpr.expression) {
|
|
||||||
// 兼容旧格式
|
|
||||||
this.generateExpression(scriptBlockExpr.expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.append('}');
|
|
||||||
}
|
|
||||||
|
|
||||||
private formatScriptBlockContent(content: string): string {
|
|
||||||
if (!content || !content.trim()) {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 应用PowerShell官方规范的格式化规则
|
|
||||||
let formatted = content.trim();
|
|
||||||
|
|
||||||
// 1. 保护所有属性访问操作符 - 这是最关键的
|
|
||||||
// 匹配所有形式的属性访问:$var.Property, $_.Property, $obj.Method.Property等
|
|
||||||
formatted = formatted.replace(/(\$[a-zA-Z_][a-zA-Z0-9_]*|\$_)\s*\.\s*([a-zA-Z_][a-zA-Z0-9_]*)/g, '$1.$2');
|
|
||||||
|
|
||||||
// 2. 保护方法调用中的点号
|
|
||||||
formatted = formatted.replace(/(\w)\s*\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g, '$1.$2(');
|
|
||||||
|
|
||||||
// 3. 确保数字单位不被分离
|
|
||||||
formatted = formatted.replace(/(\d+)\s*(KB|MB|GB|TB|PB)/gi, '$1$2');
|
|
||||||
|
|
||||||
// 4. PowerShell比较和逻辑操作符需要前后空格
|
|
||||||
const powershellOps = [
|
|
||||||
'-eq', '-ne', '-lt', '-le', '-gt', '-ge',
|
|
||||||
'-like', '-notlike', '-match', '-notmatch',
|
|
||||||
'-contains', '-notcontains', '-in', '-notin',
|
|
||||||
'-is', '-isnot', '-as', '-and', '-or', '-not', '-xor'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const op of powershellOps) {
|
|
||||||
const regex = new RegExp(`\\s*${op.replace('-', '\\-')}\\s*`, 'gi');
|
|
||||||
formatted = formatted.replace(regex, ` ${op} `);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 清理多余空格,但保护属性访问
|
|
||||||
formatted = formatted.replace(/\s{2,}/g, ' ').trim();
|
|
||||||
|
|
||||||
// 6. 最终检查:确保没有属性访问被破坏
|
|
||||||
formatted = formatted.replace(/(\$\w+|\$_)\s+\.\s*/g, '$1.');
|
|
||||||
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private generateSingleLineComment(text: string): void {
|
|
||||||
// 确保单行注释以 # 开头
|
|
||||||
const cleanText = text.startsWith('#') ? text : `# ${text}`;
|
|
||||||
this.append(cleanText);
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateMultilineComment(text: string): void {
|
|
||||||
// 多行注释保持原格式
|
|
||||||
this.append(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isLeadingComment(comment: CommentAst): boolean {
|
|
||||||
// 简单判断:如果注释在文档开头,就认为是前导注释
|
|
||||||
return comment.line <= 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
private addStatementSeparation(current: StatementAst, next: StatementAst): void {
|
|
||||||
// 函数之间添加空行
|
|
||||||
if (current.type === 'FunctionDefinition' || next.type === 'FunctionDefinition') {
|
|
||||||
this.appendLine('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 控制结构前添加空行
|
|
||||||
if (next.type === 'IfStatement' && !this.lastWasComment) {
|
|
||||||
this.appendLine('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleFinalNewline(): void {
|
|
||||||
if (this.rules.insertFinalNewline && this.output.length > 0) {
|
|
||||||
const lastLine = this.output[this.output.length - 1];
|
|
||||||
if (!lastLine.endsWith(this.rules.getNewline())) {
|
|
||||||
this.appendLine('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private postProcess(code: string): string {
|
|
||||||
let result = code;
|
|
||||||
|
|
||||||
// 清理多余的空行
|
|
||||||
if (this.rules.maxConsecutiveEmptyLines >= 0) {
|
|
||||||
const maxEmpty = this.rules.maxConsecutiveEmptyLines;
|
|
||||||
const emptyLinePattern = new RegExp(`(${this.rules.getNewline()}){${maxEmpty + 2},}`, 'g');
|
|
||||||
const replacement = this.rules.getNewline().repeat(maxEmpty + 1);
|
|
||||||
result = result.replace(emptyLinePattern, replacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理行尾空白
|
|
||||||
if (this.rules.trimTrailingWhitespace) {
|
|
||||||
result = result.replace(/ +$/gm, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助方法
|
|
||||||
private append(text: string): void {
|
|
||||||
this.output.push(text);
|
|
||||||
this.currentLineLength += text.length;
|
|
||||||
this.needsNewline = false;
|
|
||||||
this.lastWasComment = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private appendLine(text: string): void {
|
|
||||||
this.output.push(text + this.rules.getNewline());
|
|
||||||
this.currentLineLength = 0;
|
|
||||||
this.needsNewline = false;
|
|
||||||
this.lastWasComment = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private appendIndent(): void {
|
|
||||||
const indent = this.rules.getIndent(this.indentLevel);
|
|
||||||
this.append(indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ensureNewline(): void {
|
|
||||||
if (!this.needsNewline) {
|
|
||||||
this.appendLine('');
|
|
||||||
this.needsNewline = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private indent(): void {
|
|
||||||
this.indentLevel++;
|
|
||||||
}
|
|
||||||
|
|
||||||
private outdent(): void {
|
|
||||||
this.indentLevel = Math.max(0, this.indentLevel - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private shouldWrapLine(): boolean {
|
|
||||||
return this.currentLineLength > this.rules.printWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 便捷函数:格式化PowerShell AST
|
|
||||||
*/
|
|
||||||
export function formatPowerShellAST(
|
|
||||||
ast: ScriptBlockAst,
|
|
||||||
comments: CommentAst[] = [],
|
|
||||||
options: Partial<FormatterOptions> = {}
|
|
||||||
): string {
|
|
||||||
const generator = new PowerShellCodeGenerator(options);
|
|
||||||
return generator.generate(ast, comments);
|
|
||||||
}
|
|
||||||
@@ -1,440 +0,0 @@
|
|||||||
/**
|
|
||||||
* PowerShell 格式化规则引擎
|
|
||||||
* 定义各种可配置的代码格式化规则和策略
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface FormatterOptions {
|
|
||||||
// 基本格式化选项
|
|
||||||
indentSize: number; // 缩进大小
|
|
||||||
useTabsForIndentation: boolean; // 使用制表符还是空格
|
|
||||||
printWidth: number; // 行最大长度
|
|
||||||
endOfLine: 'lf' | 'crlf' | 'cr' | 'auto'; // 行尾符类型
|
|
||||||
|
|
||||||
// 空格和间距
|
|
||||||
spaceAroundOperators: boolean; // 操作符周围的空格
|
|
||||||
spaceAfterCommas: boolean; // 逗号后的空格
|
|
||||||
spaceAfterSemicolons: boolean; // 分号后的空格
|
|
||||||
spaceInsideParentheses: boolean; // 括号内的空格
|
|
||||||
spaceInsideBrackets: boolean; // 方括号内的空格
|
|
||||||
spaceInsideBraces: boolean; // 大括号内的空格
|
|
||||||
|
|
||||||
// 换行和空行
|
|
||||||
maxConsecutiveEmptyLines: number; // 最大连续空行数
|
|
||||||
insertFinalNewline: boolean; // 文件末尾插入换行符
|
|
||||||
trimTrailingWhitespace: boolean; // 删除行尾空白
|
|
||||||
blankLinesAroundFunctions: number; // 函数前后的空行数
|
|
||||||
blankLinesAroundClasses: number; // 类前后的空行数
|
|
||||||
blankLinesAroundIfStatements: boolean; // if语句前后的空行
|
|
||||||
|
|
||||||
// 括号和大括号
|
|
||||||
braceStyle: 'allman' | 'otbs' | 'stroustrup'; // 大括号风格
|
|
||||||
alwaysParenthesizeArrowFunctions: boolean; // 箭头函数总是用括号
|
|
||||||
|
|
||||||
// PowerShell特定选项
|
|
||||||
formatPipelines: boolean; // 格式化管道
|
|
||||||
pipelineStyle: 'oneline' | 'multiline' | 'auto'; // 管道风格
|
|
||||||
formatParameters: boolean; // 格式化参数
|
|
||||||
parameterAlignment: 'left' | 'right' | 'auto'; // 参数对齐方式
|
|
||||||
formatHashtables: boolean; // 格式化哈希表
|
|
||||||
hashtableStyle: 'compact' | 'expanded'; // 哈希表风格
|
|
||||||
formatArrays: boolean; // 格式化数组
|
|
||||||
arrayStyle: 'compact' | 'expanded'; // 数组风格
|
|
||||||
formatComments: boolean; // 格式化注释
|
|
||||||
commentAlignment: 'left' | 'preserve'; // 注释对齐方式
|
|
||||||
|
|
||||||
// 命名和大小写
|
|
||||||
preferredCommandCase: 'lowercase' | 'uppercase' | 'pascalcase' | 'preserve'; // 命令大小写
|
|
||||||
preferredParameterCase: 'lowercase' | 'uppercase' | 'pascalcase' | 'preserve'; // 参数大小写
|
|
||||||
preferredVariableCase: 'camelcase' | 'pascalcase' | 'preserve'; // 变量大小写
|
|
||||||
|
|
||||||
// 引号和字符串
|
|
||||||
quotestyle: 'single' | 'double' | 'preserve'; // 引号风格
|
|
||||||
escapeNonAscii: boolean; // 转义非ASCII字符
|
|
||||||
|
|
||||||
// 长度和换行
|
|
||||||
wrapLongLines: boolean; // 自动换行长行
|
|
||||||
wrapParameters: boolean; // 换行长参数列表
|
|
||||||
wrapArrays: boolean; // 换行长数组
|
|
||||||
wrapHashtables: boolean; // 换行长哈希表
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DEFAULT_OPTIONS: FormatterOptions = {
|
|
||||||
// 基本选项
|
|
||||||
indentSize: 4,
|
|
||||||
useTabsForIndentation: false,
|
|
||||||
printWidth: 120,
|
|
||||||
endOfLine: 'auto',
|
|
||||||
|
|
||||||
// 空格设置
|
|
||||||
spaceAroundOperators: true,
|
|
||||||
spaceAfterCommas: true,
|
|
||||||
spaceAfterSemicolons: true,
|
|
||||||
spaceInsideParentheses: false,
|
|
||||||
spaceInsideBrackets: false,
|
|
||||||
spaceInsideBraces: true,
|
|
||||||
|
|
||||||
// 空行设置
|
|
||||||
maxConsecutiveEmptyLines: 2,
|
|
||||||
insertFinalNewline: true,
|
|
||||||
trimTrailingWhitespace: true,
|
|
||||||
blankLinesAroundFunctions: 1,
|
|
||||||
blankLinesAroundClasses: 1,
|
|
||||||
blankLinesAroundIfStatements: false,
|
|
||||||
|
|
||||||
// 括号风格
|
|
||||||
braceStyle: 'otbs', // One True Brace Style
|
|
||||||
alwaysParenthesizeArrowFunctions: false,
|
|
||||||
|
|
||||||
// PowerShell特定
|
|
||||||
formatPipelines: true,
|
|
||||||
pipelineStyle: 'auto',
|
|
||||||
formatParameters: true,
|
|
||||||
parameterAlignment: 'left',
|
|
||||||
formatHashtables: true,
|
|
||||||
hashtableStyle: 'compact',
|
|
||||||
formatArrays: true,
|
|
||||||
arrayStyle: 'compact',
|
|
||||||
formatComments: true,
|
|
||||||
commentAlignment: 'preserve',
|
|
||||||
|
|
||||||
// 命名约定
|
|
||||||
preferredCommandCase: 'pascalcase',
|
|
||||||
preferredParameterCase: 'preserve',
|
|
||||||
preferredVariableCase: 'preserve',
|
|
||||||
|
|
||||||
// 字符串设置
|
|
||||||
quotestyle: 'preserve',
|
|
||||||
escapeNonAscii: false,
|
|
||||||
|
|
||||||
// 长度处理
|
|
||||||
wrapLongLines: true,
|
|
||||||
wrapParameters: true,
|
|
||||||
wrapArrays: true,
|
|
||||||
wrapHashtables: true
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化规则类,包含各种格式化策略的实现
|
|
||||||
*/
|
|
||||||
export class FormatterRules {
|
|
||||||
private options: FormatterOptions;
|
|
||||||
|
|
||||||
constructor(options: Partial<FormatterOptions> = {}) {
|
|
||||||
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取缩进字符串
|
|
||||||
*/
|
|
||||||
getIndent(level: number): string {
|
|
||||||
if (level <= 0) return '';
|
|
||||||
|
|
||||||
const indentChar = this.options.useTabsForIndentation ? '\t' : ' ';
|
|
||||||
const indentSize = this.options.useTabsForIndentation ? 1 : this.options.indentSize;
|
|
||||||
|
|
||||||
return indentChar.repeat(level * indentSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取换行符
|
|
||||||
*/
|
|
||||||
getNewline(): string {
|
|
||||||
switch (this.options.endOfLine) {
|
|
||||||
case 'lf': return '\n';
|
|
||||||
case 'crlf': return '\r\n';
|
|
||||||
case 'cr': return '\r';
|
|
||||||
case 'auto':
|
|
||||||
default:
|
|
||||||
// 在浏览器环境中默认使用 LF
|
|
||||||
return '\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化操作符周围的空格
|
|
||||||
*/
|
|
||||||
formatOperatorSpacing(operator: string): string {
|
|
||||||
if (!this.options.spaceAroundOperators) {
|
|
||||||
return operator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PowerShell语法中绝对不能加空格的操作符(官方规范)
|
|
||||||
const noSpaceOperators = [
|
|
||||||
'.', '::', // 属性访问和静态成员访问 - 这是PowerShell面向对象的核心
|
|
||||||
'[', ']', // 数组索引和类型转换
|
|
||||||
'(', ')', '{', '}', // 括号
|
|
||||||
'@{', // 哈希表字面量开始
|
|
||||||
';', // 哈希表和语句分隔符
|
|
||||||
'-', // cmdlet连字符(Get-ChildItem中的-)
|
|
||||||
'::' // 静态成员访问
|
|
||||||
];
|
|
||||||
|
|
||||||
if (noSpaceOperators.includes(operator)) {
|
|
||||||
return operator;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PowerShell比较操作符需要空格
|
|
||||||
const powershellOperators = ['-eq', '-ne', '-lt', '-le', '-gt', '-ge',
|
|
||||||
'-like', '-notlike', '-match', '-notmatch',
|
|
||||||
'-contains', '-notcontains', '-in', '-notin',
|
|
||||||
'-is', '-isnot', '-as', '-and', '-or', '-not', '-xor'];
|
|
||||||
|
|
||||||
if (powershellOperators.some(op => operator.toLowerCase() === op)) {
|
|
||||||
return ` ${operator} `;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 算术和赋值操作符需要空格
|
|
||||||
const spaceOperators = ['=', '+=', '-=', '*=', '/=', '%=', '+', '*', '/', '%'];
|
|
||||||
if (spaceOperators.includes(operator)) {
|
|
||||||
return ` ${operator} `;
|
|
||||||
}
|
|
||||||
|
|
||||||
return operator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化逗号后的空格
|
|
||||||
*/
|
|
||||||
formatComma(): string {
|
|
||||||
return this.options.spaceAfterCommas ? ', ' : ',';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化分号后的空格
|
|
||||||
*/
|
|
||||||
formatSemicolon(): string {
|
|
||||||
return this.options.spaceAfterSemicolons ? '; ' : ';';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化括号内的空格
|
|
||||||
*/
|
|
||||||
formatParentheses(content: string): string {
|
|
||||||
if (this.options.spaceInsideParentheses) {
|
|
||||||
return `( ${content} )`;
|
|
||||||
}
|
|
||||||
return `(${content})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化方括号内的空格
|
|
||||||
*/
|
|
||||||
formatBrackets(content: string): string {
|
|
||||||
if (this.options.spaceInsideBrackets) {
|
|
||||||
return `[ ${content} ]`;
|
|
||||||
}
|
|
||||||
return `[${content}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化大括号内的空格
|
|
||||||
*/
|
|
||||||
formatBraces(content: string): string {
|
|
||||||
if (this.options.spaceInsideBraces) {
|
|
||||||
return `{ ${content} }`;
|
|
||||||
}
|
|
||||||
return `{${content}}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取大括号的开始位置
|
|
||||||
*/
|
|
||||||
getBraceStart(): string {
|
|
||||||
switch (this.options.braceStyle) {
|
|
||||||
case 'allman':
|
|
||||||
return this.getNewline() + '{';
|
|
||||||
case 'stroustrup':
|
|
||||||
return this.getNewline() + '{';
|
|
||||||
case 'otbs':
|
|
||||||
default:
|
|
||||||
return ' {';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化命令名的大小写
|
|
||||||
*/
|
|
||||||
formatCommandCase(command: string): string {
|
|
||||||
switch (this.options.preferredCommandCase) {
|
|
||||||
case 'lowercase':
|
|
||||||
return command.toLowerCase();
|
|
||||||
case 'uppercase':
|
|
||||||
return command.toUpperCase();
|
|
||||||
case 'pascalcase':
|
|
||||||
return this.toPascalCasePreservingHyphens(command);
|
|
||||||
case 'preserve':
|
|
||||||
default:
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否应该格式化命令大小写
|
|
||||||
*/
|
|
||||||
shouldFormatCommandCase(): boolean {
|
|
||||||
return this.options.preferredCommandCase !== 'preserve';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化参数名的大小写
|
|
||||||
*/
|
|
||||||
formatParameterCase(parameter: string): string {
|
|
||||||
switch (this.options.preferredParameterCase) {
|
|
||||||
case 'lowercase':
|
|
||||||
return parameter.toLowerCase();
|
|
||||||
case 'uppercase':
|
|
||||||
return parameter.toUpperCase();
|
|
||||||
case 'pascalcase':
|
|
||||||
return this.toPascalCase(parameter);
|
|
||||||
case 'preserve':
|
|
||||||
default:
|
|
||||||
return parameter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化变量名的大小写
|
|
||||||
*/
|
|
||||||
formatVariableCase(variable: string): string {
|
|
||||||
if (!variable.startsWith('$')) {
|
|
||||||
return variable;
|
|
||||||
}
|
|
||||||
|
|
||||||
const variableName = variable.substring(1);
|
|
||||||
let formattedName: string;
|
|
||||||
|
|
||||||
switch (this.options.preferredVariableCase) {
|
|
||||||
case 'camelcase':
|
|
||||||
formattedName = this.toCamelCase(variableName);
|
|
||||||
break;
|
|
||||||
case 'pascalcase':
|
|
||||||
formattedName = this.toPascalCase(variableName);
|
|
||||||
break;
|
|
||||||
case 'preserve':
|
|
||||||
default:
|
|
||||||
formattedName = variableName;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '$' + formattedName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化字符串引号
|
|
||||||
*/
|
|
||||||
formatQuotes(value: string): string {
|
|
||||||
if (this.options.quotestyle === 'preserve') {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = this.extractStringContent(value);
|
|
||||||
|
|
||||||
switch (this.options.quotestyle) {
|
|
||||||
case 'single':
|
|
||||||
return `'${content.replace(/'/g, "''")}'`;
|
|
||||||
case 'double':
|
|
||||||
return `"${content.replace(/"/g, '""')}"`;
|
|
||||||
default:
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否需要换行
|
|
||||||
*/
|
|
||||||
shouldWrapLine(line: string): boolean {
|
|
||||||
return this.options.wrapLongLines && line.length > this.options.printWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取管道样式
|
|
||||||
*/
|
|
||||||
getPipelineStyle(elementCount: number): 'oneline' | 'multiline' {
|
|
||||||
switch (this.options.pipelineStyle) {
|
|
||||||
case 'oneline':
|
|
||||||
return 'oneline';
|
|
||||||
case 'multiline':
|
|
||||||
return 'multiline';
|
|
||||||
case 'auto':
|
|
||||||
default:
|
|
||||||
return elementCount > 2 ? 'multiline' : 'oneline';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取哈希表样式
|
|
||||||
*/
|
|
||||||
getHashtableStyle(entryCount: number): 'compact' | 'expanded' {
|
|
||||||
if (this.options.hashtableStyle === 'compact') {
|
|
||||||
return 'compact';
|
|
||||||
}
|
|
||||||
if (this.options.hashtableStyle === 'expanded') {
|
|
||||||
return 'expanded';
|
|
||||||
}
|
|
||||||
// auto logic: 对于小型哈希表默认使用compact,避免不必要的换行
|
|
||||||
return entryCount > 5 ? 'expanded' : 'compact';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取数组样式
|
|
||||||
*/
|
|
||||||
getArrayStyle(elementCount: number): 'compact' | 'expanded' {
|
|
||||||
if (this.options.arrayStyle === 'compact') {
|
|
||||||
return 'compact';
|
|
||||||
}
|
|
||||||
if (this.options.arrayStyle === 'expanded') {
|
|
||||||
return 'expanded';
|
|
||||||
}
|
|
||||||
// auto logic could be added here
|
|
||||||
return elementCount > 5 ? 'expanded' : 'compact';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助方法
|
|
||||||
private toPascalCase(str: string): string {
|
|
||||||
return str.split(/[-_\s]/)
|
|
||||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换为PascalCase但保留连字符(专门用于PowerShell cmdlet)
|
|
||||||
*/
|
|
||||||
private toPascalCasePreservingHyphens(str: string): string {
|
|
||||||
return str.split('-')
|
|
||||||
.map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
||||||
.join('-');
|
|
||||||
}
|
|
||||||
|
|
||||||
private toCamelCase(str: string): string {
|
|
||||||
const pascalCase = this.toPascalCase(str);
|
|
||||||
return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private extractStringContent(str: string): string {
|
|
||||||
if ((str.startsWith('"') && str.endsWith('"')) ||
|
|
||||||
(str.startsWith("'") && str.endsWith("'"))) {
|
|
||||||
return str.slice(1, -1);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getter methods for options
|
|
||||||
get indentSize(): number { return this.options.indentSize; }
|
|
||||||
get printWidth(): number { return this.options.printWidth; }
|
|
||||||
get maxConsecutiveEmptyLines(): number { return this.options.maxConsecutiveEmptyLines; }
|
|
||||||
get insertFinalNewline(): boolean { return this.options.insertFinalNewline; }
|
|
||||||
get trimTrailingWhitespace(): boolean { return this.options.trimTrailingWhitespace; }
|
|
||||||
get blankLinesAroundFunctions(): number { return this.options.blankLinesAroundFunctions; }
|
|
||||||
get formatPipelines(): boolean { return this.options.formatPipelines; }
|
|
||||||
get formatParameters(): boolean { return this.options.formatParameters; }
|
|
||||||
get formatHashtables(): boolean { return this.options.formatHashtables; }
|
|
||||||
get formatArrays(): boolean { return this.options.formatArrays; }
|
|
||||||
get formatComments(): boolean { return this.options.formatComments; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建规则的副本,可以重写部分选项
|
|
||||||
*/
|
|
||||||
withOptions(overrides: Partial<FormatterOptions>): FormatterRules {
|
|
||||||
return new FormatterRules({ ...this.options, ...overrides });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
/**
|
|
||||||
* Prettier Plugin for PowerShell file formatting - Modular Version
|
|
||||||
*
|
|
||||||
* This plugin provides support for formatting PowerShell files (.ps1, .psm1, .psd1)
|
|
||||||
* using a modular architecture with lexer, parser, AST, and code generator.
|
|
||||||
*/
|
|
||||||
import type { Plugin, Parser, Printer, AstPath, Doc } from 'prettier';
|
|
||||||
import { PowerShellLexer } from './lexer';
|
|
||||||
import { PowerShellParser } from './parser';
|
|
||||||
import { ScriptBlockAst, CommentAst } from './ast';
|
|
||||||
import { formatPowerShellAST } from './code-generator';
|
|
||||||
import { FormatterOptions, DEFAULT_OPTIONS } from './formatter-rules';
|
|
||||||
|
|
||||||
// PowerShell格式化结果接口
|
|
||||||
interface PowerShellParseResult {
|
|
||||||
ast: ScriptBlockAst;
|
|
||||||
comments: CommentAst[];
|
|
||||||
originalText: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parserName = 'powershell';
|
|
||||||
|
|
||||||
// 语言配置
|
|
||||||
const languages = [
|
|
||||||
{
|
|
||||||
name: 'PowerShell',
|
|
||||||
aliases: ['powershell', 'pwsh', 'posh'],
|
|
||||||
parsers: [parserName],
|
|
||||||
extensions: ['.ps1', '.psm1', '.psd1'],
|
|
||||||
filenames: ['profile.ps1'],
|
|
||||||
tmScope: 'source.powershell',
|
|
||||||
aceMode: 'powershell',
|
|
||||||
linguistLanguageId: 295,
|
|
||||||
vscodeLanguageIds: ['powershell']
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// 解析器配置
|
|
||||||
const powershellParser: Parser<PowerShellParseResult> = {
|
|
||||||
parse: parseCode,
|
|
||||||
astFormat: 'powershell',
|
|
||||||
locStart: (node: PowerShellParseResult) => 0,
|
|
||||||
locEnd: (node: PowerShellParseResult) => node.originalText.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析PowerShell代码
|
|
||||||
*/
|
|
||||||
async function parseCode(text: string, parsers?: any, options?: any): Promise<PowerShellParseResult> {
|
|
||||||
try {
|
|
||||||
// 词法分析
|
|
||||||
const lexer = new PowerShellLexer(text);
|
|
||||||
const tokens = lexer.tokenize();
|
|
||||||
|
|
||||||
// 语法分析
|
|
||||||
const parser = new PowerShellParser(tokens, text);
|
|
||||||
const ast = parser.parse();
|
|
||||||
const comments = parser.getComments();
|
|
||||||
|
|
||||||
return {
|
|
||||||
ast,
|
|
||||||
comments,
|
|
||||||
originalText: text
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('PowerShell parsing failed, using fallback:', error);
|
|
||||||
|
|
||||||
// 解析失败时,创建一个包含原始文本的简单AST
|
|
||||||
// 这样可以确保格式化失败时返回原始代码而不是空内容
|
|
||||||
return {
|
|
||||||
ast: {
|
|
||||||
type: 'ScriptBlock',
|
|
||||||
statements: [{
|
|
||||||
type: 'RawText',
|
|
||||||
value: text,
|
|
||||||
start: 0,
|
|
||||||
end: text.length,
|
|
||||||
line: 1,
|
|
||||||
column: 1
|
|
||||||
} as any],
|
|
||||||
start: 0,
|
|
||||||
end: text.length,
|
|
||||||
line: 1,
|
|
||||||
column: 1
|
|
||||||
},
|
|
||||||
comments: [],
|
|
||||||
originalText: text
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PowerShell代码打印器
|
|
||||||
*/
|
|
||||||
const printPowerShell = (path: AstPath<PowerShellParseResult>, options: any): Doc => {
|
|
||||||
const parseResult = path.node;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 构建格式化选项 - 优先保持原有格式,避免破坏PowerShell语法
|
|
||||||
const formatterOptions: Partial<FormatterOptions> = {
|
|
||||||
indentSize: options.tabWidth || DEFAULT_OPTIONS.indentSize,
|
|
||||||
useTabsForIndentation: options.useTabs || DEFAULT_OPTIONS.useTabsForIndentation,
|
|
||||||
printWidth: options.printWidth || DEFAULT_OPTIONS.printWidth,
|
|
||||||
spaceAroundOperators: true,
|
|
||||||
formatPipelines: true,
|
|
||||||
formatParameters: true,
|
|
||||||
formatHashtables: true,
|
|
||||||
hashtableStyle: 'compact', // 强制使用紧凑格式,避免不必要的换行
|
|
||||||
formatArrays: true,
|
|
||||||
arrayStyle: 'compact',
|
|
||||||
formatComments: true,
|
|
||||||
maxConsecutiveEmptyLines: 1,
|
|
||||||
insertFinalNewline: true,
|
|
||||||
trimTrailingWhitespace: true,
|
|
||||||
blankLinesAroundFunctions: 1,
|
|
||||||
braceStyle: 'otbs',
|
|
||||||
preferredCommandCase: 'preserve', // 保持原有命令大小写,不破坏语法
|
|
||||||
preferredParameterCase: 'preserve',
|
|
||||||
preferredVariableCase: 'preserve',
|
|
||||||
quotestyle: 'preserve',
|
|
||||||
wrapLongLines: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// 使用新的模块化格式化器
|
|
||||||
const formattedCode = formatPowerShellAST(
|
|
||||||
parseResult.ast,
|
|
||||||
parseResult.comments,
|
|
||||||
formatterOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
return formattedCode;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('PowerShell formatting failed, returning original code:', error);
|
|
||||||
return parseResult.originalText;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 打印器配置
|
|
||||||
const powershellPrinter: Printer<PowerShellParseResult> = {
|
|
||||||
print: printPowerShell,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 插件选项配置
|
|
||||||
const options = {
|
|
||||||
// PowerShell特定格式化选项
|
|
||||||
powershellBraceStyle: {
|
|
||||||
type: 'choice' as const,
|
|
||||||
category: 'PowerShell',
|
|
||||||
default: DEFAULT_OPTIONS.braceStyle,
|
|
||||||
description: 'PowerShell大括号样式',
|
|
||||||
choices: [
|
|
||||||
{ value: 'allman', description: 'Allman风格(大括号另起一行)' },
|
|
||||||
{ value: 'otbs', description: '1TBS风格(大括号同行)' },
|
|
||||||
{ value: 'stroustrup', description: 'Stroustrup风格' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
powershellCommandCase: {
|
|
||||||
type: 'choice' as const,
|
|
||||||
category: 'PowerShell',
|
|
||||||
default: DEFAULT_OPTIONS.preferredCommandCase,
|
|
||||||
description: 'PowerShell命令大小写风格',
|
|
||||||
choices: [
|
|
||||||
{ value: 'lowercase', description: '小写' },
|
|
||||||
{ value: 'uppercase', description: '大写' },
|
|
||||||
{ value: 'pascalcase', description: 'Pascal大小写' },
|
|
||||||
{ value: 'preserve', description: '保持原样' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
powershellPipelineStyle: {
|
|
||||||
type: 'choice' as const,
|
|
||||||
category: 'PowerShell',
|
|
||||||
default: DEFAULT_OPTIONS.pipelineStyle,
|
|
||||||
description: 'PowerShell管道样式',
|
|
||||||
choices: [
|
|
||||||
{ value: 'oneline', description: '单行' },
|
|
||||||
{ value: 'multiline', description: '多行' },
|
|
||||||
{ value: 'auto', description: '自动' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
powershellSpaceAroundOperators: {
|
|
||||||
type: 'boolean' as const,
|
|
||||||
category: 'PowerShell',
|
|
||||||
default: DEFAULT_OPTIONS.spaceAroundOperators,
|
|
||||||
description: '在操作符周围添加空格'
|
|
||||||
},
|
|
||||||
powershellMaxEmptyLines: {
|
|
||||||
type: 'int' as const,
|
|
||||||
category: 'PowerShell',
|
|
||||||
default: DEFAULT_OPTIONS.maxConsecutiveEmptyLines,
|
|
||||||
description: '最大连续空行数'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const powershellPlugin: Plugin = {
|
|
||||||
languages,
|
|
||||||
parsers: {
|
|
||||||
[parserName]: powershellParser,
|
|
||||||
},
|
|
||||||
printers: {
|
|
||||||
[parserName]: powershellPrinter,
|
|
||||||
},
|
|
||||||
options,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default powershellPlugin;
|
|
||||||
export { languages };
|
|
||||||
export const parsers = powershellPlugin.parsers;
|
|
||||||
export const printers = powershellPlugin.printers;
|
|
||||||
@@ -1,722 +0,0 @@
|
|||||||
/**
|
|
||||||
* PowerShell 词法分析器 (Lexer)
|
|
||||||
* 将PowerShell代码分解为tokens,用于后续的语法分析和格式化
|
|
||||||
*/
|
|
||||||
|
|
||||||
export enum TokenType {
|
|
||||||
// 字面量
|
|
||||||
STRING = 'STRING',
|
|
||||||
NUMBER = 'NUMBER',
|
|
||||||
VARIABLE = 'VARIABLE',
|
|
||||||
|
|
||||||
// 关键字
|
|
||||||
KEYWORD = 'KEYWORD',
|
|
||||||
FUNCTION = 'FUNCTION',
|
|
||||||
|
|
||||||
// 操作符
|
|
||||||
OPERATOR = 'OPERATOR',
|
|
||||||
ASSIGNMENT = 'ASSIGNMENT',
|
|
||||||
COMPARISON = 'COMPARISON',
|
|
||||||
LOGICAL = 'LOGICAL',
|
|
||||||
ARITHMETIC = 'ARITHMETIC',
|
|
||||||
|
|
||||||
// 分隔符
|
|
||||||
LEFT_PAREN = 'LEFT_PAREN',
|
|
||||||
RIGHT_PAREN = 'RIGHT_PAREN',
|
|
||||||
LEFT_BRACE = 'LEFT_BRACE',
|
|
||||||
RIGHT_BRACE = 'RIGHT_BRACE',
|
|
||||||
LEFT_BRACKET = 'LEFT_BRACKET',
|
|
||||||
RIGHT_BRACKET = 'RIGHT_BRACKET',
|
|
||||||
SEMICOLON = 'SEMICOLON',
|
|
||||||
COMMA = 'COMMA',
|
|
||||||
DOT = 'DOT',
|
|
||||||
PIPE = 'PIPE',
|
|
||||||
|
|
||||||
// 特殊
|
|
||||||
WHITESPACE = 'WHITESPACE',
|
|
||||||
NEWLINE = 'NEWLINE',
|
|
||||||
COMMENT = 'COMMENT',
|
|
||||||
MULTILINE_COMMENT = 'MULTILINE_COMMENT',
|
|
||||||
HERE_STRING = 'HERE_STRING',
|
|
||||||
|
|
||||||
// 控制结构
|
|
||||||
IF = 'IF',
|
|
||||||
ELSE = 'ELSE',
|
|
||||||
ELSEIF = 'ELSEIF',
|
|
||||||
WHILE = 'WHILE',
|
|
||||||
FOR = 'FOR',
|
|
||||||
FOREACH = 'FOREACH',
|
|
||||||
SWITCH = 'SWITCH',
|
|
||||||
TRY = 'TRY',
|
|
||||||
CATCH = 'CATCH',
|
|
||||||
FINALLY = 'FINALLY',
|
|
||||||
|
|
||||||
// 其他
|
|
||||||
IDENTIFIER = 'IDENTIFIER',
|
|
||||||
CMDLET = 'CMDLET',
|
|
||||||
PARAMETER = 'PARAMETER',
|
|
||||||
EOF = 'EOF',
|
|
||||||
UNKNOWN = 'UNKNOWN'
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Token {
|
|
||||||
type: TokenType;
|
|
||||||
value: string;
|
|
||||||
line: number;
|
|
||||||
column: number;
|
|
||||||
startIndex: number;
|
|
||||||
endIndex: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PowerShellLexer {
|
|
||||||
private code: string;
|
|
||||||
private position: number = 0;
|
|
||||||
private line: number = 1;
|
|
||||||
private column: number = 1;
|
|
||||||
private tokens: Token[] = [];
|
|
||||||
|
|
||||||
// PowerShell关键字
|
|
||||||
private readonly keywords = new Set([
|
|
||||||
'if', 'else', 'elseif', 'switch', 'while', 'for', 'foreach', 'do',
|
|
||||||
'try', 'catch', 'finally', 'throw', 'return', 'break', 'continue',
|
|
||||||
'function', 'filter', 'param', 'begin', 'process', 'end',
|
|
||||||
'class', 'enum', 'using', 'namespace', 'workflow', 'configuration',
|
|
||||||
'dynamicparam', 'exit'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// PowerShell比较操作符
|
|
||||||
private readonly comparisonOperators = new Set([
|
|
||||||
'-eq', '-ne', '-lt', '-le', '-gt', '-ge',
|
|
||||||
'-like', '-notlike', '-match', '-notmatch',
|
|
||||||
'-contains', '-notcontains', '-in', '-notin',
|
|
||||||
'-is', '-isnot', '-as'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// PowerShell逻辑操作符
|
|
||||||
private readonly logicalOperators = new Set([
|
|
||||||
'-and', '-or', '-not', '-xor', '-band', '-bor', '-bxor', '-bnot'
|
|
||||||
]);
|
|
||||||
|
|
||||||
constructor(code: string) {
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对代码进行词法分析,返回token数组
|
|
||||||
*/
|
|
||||||
public tokenize(): Token[] {
|
|
||||||
this.position = 0;
|
|
||||||
this.line = 1;
|
|
||||||
this.column = 1;
|
|
||||||
this.tokens = [];
|
|
||||||
|
|
||||||
while (this.position < this.code.length) {
|
|
||||||
this.skipWhitespace();
|
|
||||||
|
|
||||||
if (this.position >= this.code.length) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = this.nextToken();
|
|
||||||
if (token) {
|
|
||||||
this.tokens.push(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tokens.push({
|
|
||||||
type: TokenType.EOF,
|
|
||||||
value: '',
|
|
||||||
line: this.line,
|
|
||||||
column: this.column,
|
|
||||||
startIndex: this.position,
|
|
||||||
endIndex: this.position
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
private nextToken(): Token | null {
|
|
||||||
const startPos = this.position;
|
|
||||||
const startLine = this.line;
|
|
||||||
const startColumn = this.column;
|
|
||||||
|
|
||||||
const char = this.code[this.position];
|
|
||||||
|
|
||||||
// 处理换行
|
|
||||||
if (char === '\n') {
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.NEWLINE, '\n', startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理注释
|
|
||||||
if (char === '#') {
|
|
||||||
return this.tokenizeComment(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理多行注释
|
|
||||||
if (char === '<' && this.peek() === '#') {
|
|
||||||
return this.tokenizeMultilineComment(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理字符串
|
|
||||||
if (char === '"' || char === "'") {
|
|
||||||
return this.tokenizeString(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理Here-String
|
|
||||||
if (char === '@' && (this.peek() === '"' || this.peek() === "'")) {
|
|
||||||
return this.tokenizeHereString(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理哈希表字面量 @{
|
|
||||||
if (char === '@' && this.peek() === '{') {
|
|
||||||
this.advance(); // skip '@'
|
|
||||||
this.advance(); // skip '{'
|
|
||||||
return this.createToken(TokenType.LEFT_BRACE, '@{', startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理变量
|
|
||||||
if (char === '$') {
|
|
||||||
return this.tokenizeVariable(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理数字
|
|
||||||
if (this.isDigit(char) || (char === '.' && this.isDigit(this.peek()))) {
|
|
||||||
return this.tokenizeNumber(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理操作符和分隔符
|
|
||||||
const operatorToken = this.tokenizeOperator(startPos, startLine, startColumn);
|
|
||||||
if (operatorToken) {
|
|
||||||
return operatorToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 优先处理PowerShell比较操作符(以-开头)
|
|
||||||
if (char === '-' && this.isIdentifierStart(this.peek())) {
|
|
||||||
const potentialOperator = this.peekPowerShellOperator();
|
|
||||||
if (potentialOperator) {
|
|
||||||
return this.tokenizePowerShellOperator(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
// 如果不是操作符,可能是参数
|
|
||||||
return this.tokenizeParameter(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理标识符(包括cmdlet和关键字)
|
|
||||||
if (this.isIdentifierStart(char)) {
|
|
||||||
return this.tokenizeIdentifier(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理PowerShell特殊字符
|
|
||||||
if (char === '?') {
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.OPERATOR, char, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理独立的减号(可能是负数或减法)
|
|
||||||
if (char === '-') {
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.ARITHMETIC, char, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理其他可能的特殊字符,作为标识符处理而不是未知字符
|
|
||||||
if (this.isPrintableChar(char)) {
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.IDENTIFIER, char, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 真正的未知字符(非打印字符等)
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.UNKNOWN, char, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeComment(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
let value = '';
|
|
||||||
while (this.position < this.code.length && this.code[this.position] !== '\n') {
|
|
||||||
value += this.code[this.position];
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
return this.createToken(TokenType.COMMENT, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeMultilineComment(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
let value = '';
|
|
||||||
this.advance(); // skip '<'
|
|
||||||
this.advance(); // skip '#'
|
|
||||||
value += '<#';
|
|
||||||
|
|
||||||
while (this.position < this.code.length - 1) {
|
|
||||||
if (this.code[this.position] === '#' && this.code[this.position + 1] === '>') {
|
|
||||||
value += '#>';
|
|
||||||
this.advance();
|
|
||||||
this.advance();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
value += this.code[this.position];
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createToken(TokenType.MULTILINE_COMMENT, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeString(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
const quote = this.code[this.position];
|
|
||||||
let value = quote;
|
|
||||||
this.advance();
|
|
||||||
|
|
||||||
while (this.position < this.code.length) {
|
|
||||||
const char = this.code[this.position];
|
|
||||||
value += char;
|
|
||||||
|
|
||||||
if (char === quote) {
|
|
||||||
this.advance();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理转义字符
|
|
||||||
if (char === '`' && quote === '"') {
|
|
||||||
this.advance();
|
|
||||||
if (this.position < this.code.length) {
|
|
||||||
value += this.code[this.position];
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createToken(TokenType.STRING, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeHereString(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
const quote = this.code[this.position + 1]; // " or '
|
|
||||||
let value = `@${quote}`;
|
|
||||||
this.advance(); // skip '@'
|
|
||||||
this.advance(); // skip quote
|
|
||||||
|
|
||||||
while (this.position < this.code.length - 1) {
|
|
||||||
if (this.code[this.position] === quote && this.code[this.position + 1] === '@') {
|
|
||||||
value += `${quote}@`;
|
|
||||||
this.advance();
|
|
||||||
this.advance();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
value += this.code[this.position];
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createToken(TokenType.HERE_STRING, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeVariable(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
let value = '$';
|
|
||||||
this.advance(); // skip '$'
|
|
||||||
|
|
||||||
// 处理特殊变量如 $_, $$, $^
|
|
||||||
const specialVars = ['_', '$', '^', '?'];
|
|
||||||
if (specialVars.includes(this.code[this.position])) {
|
|
||||||
value += this.code[this.position];
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.VARIABLE, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理大括号变量 ${variable name}
|
|
||||||
if (this.code[this.position] === '{') {
|
|
||||||
this.advance(); // skip '{'
|
|
||||||
value += '{';
|
|
||||||
while (this.position < this.code.length && this.code[this.position] !== '}') {
|
|
||||||
value += this.code[this.position];
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
if (this.position < this.code.length) {
|
|
||||||
value += '}';
|
|
||||||
this.advance(); // skip '}'
|
|
||||||
}
|
|
||||||
return this.createToken(TokenType.VARIABLE, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 普通变量名
|
|
||||||
while (this.position < this.code.length && this.isIdentifierChar(this.code[this.position])) {
|
|
||||||
value += this.code[this.position];
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createToken(TokenType.VARIABLE, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeNumber(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
let value = '';
|
|
||||||
let hasDecimal = false;
|
|
||||||
|
|
||||||
while (this.position < this.code.length) {
|
|
||||||
const char = this.code[this.position];
|
|
||||||
|
|
||||||
if (this.isDigit(char)) {
|
|
||||||
value += char;
|
|
||||||
this.advance();
|
|
||||||
} else if (char === '.' && !hasDecimal && this.isDigit(this.peek())) {
|
|
||||||
hasDecimal = true;
|
|
||||||
value += char;
|
|
||||||
this.advance();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有PowerShell数字单位后缀(KB, MB, GB, TB, PB)
|
|
||||||
const unitPattern = /^(KB|MB|GB|TB|PB)/i;
|
|
||||||
const remainingCode = this.code.substring(this.position);
|
|
||||||
const unitMatch = remainingCode.match(unitPattern);
|
|
||||||
|
|
||||||
if (unitMatch) {
|
|
||||||
value += unitMatch[0]; // 使用 [0] 获取完整匹配
|
|
||||||
// 移动position到单位后面
|
|
||||||
for (let i = 0; i < unitMatch[0].length; i++) {
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createToken(TokenType.NUMBER, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeOperator(startPos: number, startLine: number, startColumn: number): Token | null {
|
|
||||||
const char = this.code[this.position];
|
|
||||||
|
|
||||||
// 双字符操作符
|
|
||||||
const twoChar = this.code.substring(this.position, this.position + 2);
|
|
||||||
const doubleOperators = ['==', '!=', '<=', '>=', '++', '--', '+=', '-=', '*=', '/=', '%='];
|
|
||||||
|
|
||||||
if (doubleOperators.includes(twoChar)) {
|
|
||||||
this.advance();
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.OPERATOR, twoChar, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 单字符操作符
|
|
||||||
switch (char) {
|
|
||||||
case '=':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.ASSIGNMENT, char, startPos, startLine, startColumn);
|
|
||||||
case '+':
|
|
||||||
case '*':
|
|
||||||
case '/':
|
|
||||||
case '%':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.ARITHMETIC, char, startPos, startLine, startColumn);
|
|
||||||
case '-':
|
|
||||||
// 不在这里处理'-',让PowerShell操作符检查优先处理
|
|
||||||
return null;
|
|
||||||
case '(':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.LEFT_PAREN, char, startPos, startLine, startColumn);
|
|
||||||
case ')':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.RIGHT_PAREN, char, startPos, startLine, startColumn);
|
|
||||||
case '{':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.LEFT_BRACE, char, startPos, startLine, startColumn);
|
|
||||||
case '}':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.RIGHT_BRACE, char, startPos, startLine, startColumn);
|
|
||||||
case '[':
|
|
||||||
// 检查是否是PowerShell类型转换 [type]
|
|
||||||
const typePattern = this.peekTypeConversion();
|
|
||||||
if (typePattern) {
|
|
||||||
return this.tokenizeTypeConversion(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.LEFT_BRACKET, char, startPos, startLine, startColumn);
|
|
||||||
case ']':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.RIGHT_BRACKET, char, startPos, startLine, startColumn);
|
|
||||||
case ';':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.SEMICOLON, char, startPos, startLine, startColumn);
|
|
||||||
case ',':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.COMMA, char, startPos, startLine, startColumn);
|
|
||||||
case '.':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.DOT, char, startPos, startLine, startColumn);
|
|
||||||
case '|':
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.PIPE, char, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeIdentifier(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
let value = '';
|
|
||||||
|
|
||||||
// 改进的标识符识别,支持PowerShell cmdlet格式(动词-名词)
|
|
||||||
while (this.position < this.code.length) {
|
|
||||||
const char = this.code[this.position];
|
|
||||||
|
|
||||||
if (this.isIdentifierChar(char)) {
|
|
||||||
value += char;
|
|
||||||
this.advance();
|
|
||||||
} else if (char === '-' && value.length > 0 && this.isIdentifierStart(this.peek())) {
|
|
||||||
// 检查是否是cmdlet格式(动词-名词)
|
|
||||||
const nextPart = this.peekIdentifierPart();
|
|
||||||
if (nextPart && !this.isPowerShellOperator('-' + nextPart)) {
|
|
||||||
// 这是cmdlet名字的一部分,继续
|
|
||||||
value += char;
|
|
||||||
this.advance();
|
|
||||||
} else {
|
|
||||||
// 这可能是操作符,停止
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const lowerValue = value.toLowerCase();
|
|
||||||
|
|
||||||
// 检查是否是关键字
|
|
||||||
if (this.keywords.has(lowerValue)) {
|
|
||||||
return this.createToken(this.getKeywordTokenType(lowerValue), value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是函数(以动词-名词格式)
|
|
||||||
if (this.isCmdletName(value)) {
|
|
||||||
return this.createToken(TokenType.CMDLET, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createToken(TokenType.IDENTIFIER, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeParameter(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
let value = '';
|
|
||||||
|
|
||||||
while (this.position < this.code.length && (this.isIdentifierChar(this.code[this.position]) || this.code[this.position] === '-')) {
|
|
||||||
value += this.code[this.position];
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
const lowerValue = value.toLowerCase();
|
|
||||||
|
|
||||||
// 检查是否是比较操作符
|
|
||||||
if (this.comparisonOperators.has(lowerValue)) {
|
|
||||||
return this.createToken(TokenType.COMPARISON, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是逻辑操作符
|
|
||||||
if (this.logicalOperators.has(lowerValue)) {
|
|
||||||
return this.createToken(TokenType.LOGICAL, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createToken(TokenType.PARAMETER, value, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getKeywordTokenType(keyword: string): TokenType {
|
|
||||||
switch (keyword) {
|
|
||||||
case 'if': return TokenType.IF;
|
|
||||||
case 'else': return TokenType.ELSE;
|
|
||||||
case 'elseif': return TokenType.ELSEIF;
|
|
||||||
case 'while': return TokenType.WHILE;
|
|
||||||
case 'for': return TokenType.FOR;
|
|
||||||
case 'foreach': return TokenType.FOREACH;
|
|
||||||
case 'switch': return TokenType.SWITCH;
|
|
||||||
case 'try': return TokenType.TRY;
|
|
||||||
case 'catch': return TokenType.CATCH;
|
|
||||||
case 'finally': return TokenType.FINALLY;
|
|
||||||
case 'function': return TokenType.FUNCTION;
|
|
||||||
default: return TokenType.KEYWORD;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private isCmdletName(name: string): boolean {
|
|
||||||
// PowerShell cmdlet通常遵循 Verb-Noun 格式,可能包含多个连字符
|
|
||||||
const verbNounPattern = /^[A-Za-z]+(-[A-Za-z]+)+$/;
|
|
||||||
return verbNounPattern.test(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private peekPowerShellOperator(): string | null {
|
|
||||||
// 检查是否是PowerShell比较或逻辑操作符
|
|
||||||
const operatorPatterns = [
|
|
||||||
'-eq', '-ne', '-lt', '-le', '-gt', '-ge',
|
|
||||||
'-like', '-notlike', '-match', '-notmatch',
|
|
||||||
'-contains', '-notcontains', '-in', '-notin',
|
|
||||||
'-is', '-isnot', '-as',
|
|
||||||
'-and', '-or', '-not', '-xor',
|
|
||||||
'-band', '-bor', '-bxor', '-bnot'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const op of operatorPatterns) {
|
|
||||||
if (this.matchesOperator(op)) {
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private matchesOperator(operator: string): boolean {
|
|
||||||
if (this.position + operator.length > this.code.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const substr = this.code.substring(this.position, this.position + operator.length);
|
|
||||||
if (substr.toLowerCase() !== operator.toLowerCase()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保操作符后面不是字母数字字符(避免匹配部分单词)
|
|
||||||
const nextChar = this.position + operator.length < this.code.length
|
|
||||||
? this.code[this.position + operator.length]
|
|
||||||
: ' ';
|
|
||||||
return !this.isIdentifierChar(nextChar);
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizePowerShellOperator(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
const operator = this.peekPowerShellOperator();
|
|
||||||
if (!operator) {
|
|
||||||
// 如果不是操作符,作为参数处理
|
|
||||||
return this.tokenizeParameter(startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消费操作符字符
|
|
||||||
for (let i = 0; i < operator.length; i++) {
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
const lowerOp = operator.toLowerCase();
|
|
||||||
|
|
||||||
// 确定操作符类型
|
|
||||||
if (this.comparisonOperators.has(lowerOp)) {
|
|
||||||
return this.createToken(TokenType.COMPARISON, operator, startPos, startLine, startColumn);
|
|
||||||
} else if (this.logicalOperators.has(lowerOp)) {
|
|
||||||
return this.createToken(TokenType.LOGICAL, operator, startPos, startLine, startColumn);
|
|
||||||
} else {
|
|
||||||
return this.createToken(TokenType.OPERATOR, operator, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private peekIdentifierPart(): string | null {
|
|
||||||
if (this.position + 1 >= this.code.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = '';
|
|
||||||
let pos = this.position + 1; // 跳过连字符
|
|
||||||
|
|
||||||
while (pos < this.code.length && this.isIdentifierChar(this.code[pos])) {
|
|
||||||
result += this.code[pos];
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.length > 0 ? result : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isPowerShellOperator(text: string): boolean {
|
|
||||||
const lowerText = text.toLowerCase();
|
|
||||||
return this.comparisonOperators.has(lowerText) || this.logicalOperators.has(lowerText);
|
|
||||||
}
|
|
||||||
|
|
||||||
private peekTypeConversion(): string | null {
|
|
||||||
// 检查是否是PowerShell类型转换,如 [int], [string], [datetime] 等
|
|
||||||
if (this.code[this.position] !== '[') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pos = this.position + 1; // 跳过 '['
|
|
||||||
let typeContent = '';
|
|
||||||
|
|
||||||
// 查找类型名称
|
|
||||||
while (pos < this.code.length && this.code[pos] !== ']') {
|
|
||||||
typeContent += this.code[pos];
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos >= this.code.length || this.code[pos] !== ']') {
|
|
||||||
return null; // 没有找到匹配的 ']'
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否是有效的PowerShell类型
|
|
||||||
const validTypes = [
|
|
||||||
'int', 'int32', 'int64', 'string', 'bool', 'boolean', 'char', 'byte',
|
|
||||||
'double', 'float', 'decimal', 'long', 'short', 'datetime', 'timespan',
|
|
||||||
'array', 'hashtable', 'object', 'psobject', 'xml', 'scriptblock',
|
|
||||||
'guid', 'uri', 'version', 'regex', 'mailaddress', 'ipaddress'
|
|
||||||
];
|
|
||||||
|
|
||||||
const lowerType = typeContent.toLowerCase().trim();
|
|
||||||
if (validTypes.includes(lowerType) || lowerType.includes('.')) {
|
|
||||||
return `[${typeContent}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private tokenizeTypeConversion(startPos: number, startLine: number, startColumn: number): Token {
|
|
||||||
const typeConversion = this.peekTypeConversion();
|
|
||||||
if (!typeConversion) {
|
|
||||||
// 这不应该发生,但作为安全措施
|
|
||||||
this.advance();
|
|
||||||
return this.createToken(TokenType.LEFT_BRACKET, '[', startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 消费整个类型转换
|
|
||||||
for (let i = 0; i < typeConversion.length; i++) {
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.createToken(TokenType.IDENTIFIER, typeConversion, startPos, startLine, startColumn);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isIdentifierStart(char: string): boolean {
|
|
||||||
return /[a-zA-Z_]/.test(char);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isIdentifierChar(char: string): boolean {
|
|
||||||
return /[a-zA-Z0-9_]/.test(char);
|
|
||||||
}
|
|
||||||
|
|
||||||
private isDigit(char: string): boolean {
|
|
||||||
return char >= '0' && char <= '9';
|
|
||||||
}
|
|
||||||
|
|
||||||
private isPrintableChar(char: string): boolean {
|
|
||||||
// 检查是否为可打印字符(非控制字符)
|
|
||||||
const charCode = char.charCodeAt(0);
|
|
||||||
return charCode >= 32 && charCode <= 126;
|
|
||||||
}
|
|
||||||
|
|
||||||
private advance(): void {
|
|
||||||
if (this.position < this.code.length) {
|
|
||||||
if (this.code[this.position] === '\n') {
|
|
||||||
this.line++;
|
|
||||||
this.column = 1;
|
|
||||||
} else {
|
|
||||||
this.column++;
|
|
||||||
}
|
|
||||||
this.position++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private peek(): string {
|
|
||||||
return this.position + 1 < this.code.length ? this.code[this.position + 1] : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
private skipWhitespace(): void {
|
|
||||||
while (this.position < this.code.length) {
|
|
||||||
const char = this.code[this.position];
|
|
||||||
if (char === ' ' || char === '\t' || char === '\r') {
|
|
||||||
this.advance();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private createToken(type: TokenType, value: string, startPos: number, line: number, column: number): Token {
|
|
||||||
return {
|
|
||||||
type,
|
|
||||||
value,
|
|
||||||
line,
|
|
||||||
column,
|
|
||||||
startIndex: startPos,
|
|
||||||
endIndex: this.position
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,821 +0,0 @@
|
|||||||
/**
|
|
||||||
* PowerShell 语法分析器 (Parser)
|
|
||||||
* 将词法分析器产生的tokens转换为抽象语法树(AST)
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Token, TokenType } from './lexer';
|
|
||||||
import {
|
|
||||||
ASTNode,
|
|
||||||
ScriptBlockAst,
|
|
||||||
StatementAst,
|
|
||||||
ExpressionAst,
|
|
||||||
PipelineAst,
|
|
||||||
CommandAst,
|
|
||||||
AssignmentAst,
|
|
||||||
VariableAst,
|
|
||||||
LiteralAst,
|
|
||||||
BinaryExpressionAst,
|
|
||||||
IfStatementAst,
|
|
||||||
FunctionDefinitionAst,
|
|
||||||
ParameterAst,
|
|
||||||
ASTNodeFactory,
|
|
||||||
CommentAst,
|
|
||||||
PipelineElementAst,
|
|
||||||
ElseIfClauseAst,
|
|
||||||
UnaryExpressionAst,
|
|
||||||
ParenthesizedExpressionAst
|
|
||||||
} from './ast';
|
|
||||||
|
|
||||||
export class PowerShellParser {
|
|
||||||
private tokens: Token[];
|
|
||||||
private currentIndex: number = 0;
|
|
||||||
private comments: CommentAst[] = [];
|
|
||||||
|
|
||||||
private originalCode: string;
|
|
||||||
|
|
||||||
constructor(tokens: Token[], originalCode: string = '') {
|
|
||||||
this.tokens = tokens;
|
|
||||||
this.currentIndex = 0;
|
|
||||||
this.originalCode = originalCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析tokens生成AST
|
|
||||||
*/
|
|
||||||
public parse(): ScriptBlockAst {
|
|
||||||
const statements: StatementAst[] = [];
|
|
||||||
|
|
||||||
while (!this.isAtEnd()) {
|
|
||||||
// 跳过空白和换行
|
|
||||||
this.skipWhitespaceAndNewlines();
|
|
||||||
|
|
||||||
if (this.isAtEnd()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理注释
|
|
||||||
if (this.match(TokenType.COMMENT, TokenType.MULTILINE_COMMENT)) {
|
|
||||||
const comment = this.parseComment();
|
|
||||||
this.comments.push(comment);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const statement = this.parseStatement();
|
|
||||||
if (statement) {
|
|
||||||
statements.push(statement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const start = this.tokens.length > 0 ? this.tokens[0].startIndex : 0;
|
|
||||||
const end = this.tokens.length > 0 ? this.tokens[this.tokens.length - 1].endIndex : 0;
|
|
||||||
const line = this.tokens.length > 0 ? this.tokens[0].line : 1;
|
|
||||||
const column = this.tokens.length > 0 ? this.tokens[0].column : 1;
|
|
||||||
|
|
||||||
return ASTNodeFactory.createScriptBlock(statements, start, end, line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getComments(): CommentAst[] {
|
|
||||||
return this.comments;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseStatement(): StatementAst | null {
|
|
||||||
// 函数定义
|
|
||||||
if (this.check(TokenType.FUNCTION)) {
|
|
||||||
return this.parseFunctionDefinition();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 控制流语句
|
|
||||||
if (this.check(TokenType.IF)) {
|
|
||||||
return this.parseIfStatement();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 赋值或管道
|
|
||||||
return this.parsePipeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseFunctionDefinition(): FunctionDefinitionAst {
|
|
||||||
const start = this.current().startIndex;
|
|
||||||
const line = this.current().line;
|
|
||||||
const column = this.current().column;
|
|
||||||
|
|
||||||
this.consume(TokenType.FUNCTION, "Expected 'function'");
|
|
||||||
|
|
||||||
// 函数名可能是CMDLET类型(如Get-Something)或IDENTIFIER
|
|
||||||
let nameToken: Token;
|
|
||||||
if (this.check(TokenType.CMDLET)) {
|
|
||||||
nameToken = this.consume(TokenType.CMDLET, "Expected function name");
|
|
||||||
} else {
|
|
||||||
nameToken = this.consume(TokenType.IDENTIFIER, "Expected function name");
|
|
||||||
}
|
|
||||||
const name = nameToken.value;
|
|
||||||
|
|
||||||
// 解析参数
|
|
||||||
const parameters: ParameterAst[] = [];
|
|
||||||
if (this.match(TokenType.LEFT_PAREN)) {
|
|
||||||
if (!this.check(TokenType.RIGHT_PAREN)) {
|
|
||||||
do {
|
|
||||||
const param = this.parseParameter();
|
|
||||||
if (param) {
|
|
||||||
parameters.push(param);
|
|
||||||
}
|
|
||||||
} while (this.match(TokenType.COMMA));
|
|
||||||
}
|
|
||||||
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析函数体
|
|
||||||
const body = this.parseScriptBlock();
|
|
||||||
|
|
||||||
const end = this.previous().endIndex;
|
|
||||||
|
|
||||||
return ASTNodeFactory.createFunctionDefinition(name, parameters, body, start, end, line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseIfStatement(): IfStatementAst {
|
|
||||||
const start = this.current().startIndex;
|
|
||||||
const line = this.current().line;
|
|
||||||
const column = this.current().column;
|
|
||||||
|
|
||||||
this.consume(TokenType.IF, "Expected 'if'");
|
|
||||||
|
|
||||||
// PowerShell的if语句可能有括号,也可能没有
|
|
||||||
const hasParens = this.check(TokenType.LEFT_PAREN);
|
|
||||||
if (hasParens) {
|
|
||||||
this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'if'");
|
|
||||||
}
|
|
||||||
|
|
||||||
const condition = this.parseExpression();
|
|
||||||
|
|
||||||
if (hasParens) {
|
|
||||||
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after if condition");
|
|
||||||
}
|
|
||||||
|
|
||||||
const ifBody = this.parseScriptBlock();
|
|
||||||
|
|
||||||
const elseIfClauses: ElseIfClauseAst[] = [];
|
|
||||||
let elseBody: ScriptBlockAst | undefined;
|
|
||||||
|
|
||||||
// 处理 elseif 子句
|
|
||||||
while (this.match(TokenType.ELSEIF)) {
|
|
||||||
const elseIfStart = this.previous().startIndex;
|
|
||||||
const elseIfLine = this.previous().line;
|
|
||||||
const elseIfColumn = this.previous().column;
|
|
||||||
|
|
||||||
this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'elseif'");
|
|
||||||
const elseIfCondition = this.parseExpression();
|
|
||||||
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after elseif condition");
|
|
||||||
const elseIfBody = this.parseScriptBlock();
|
|
||||||
|
|
||||||
const elseIfEnd = this.previous().endIndex;
|
|
||||||
|
|
||||||
elseIfClauses.push({
|
|
||||||
type: 'ElseIfClause',
|
|
||||||
condition: elseIfCondition,
|
|
||||||
body: elseIfBody,
|
|
||||||
start: elseIfStart,
|
|
||||||
end: elseIfEnd,
|
|
||||||
line: elseIfLine,
|
|
||||||
column: elseIfColumn
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理 else 子句
|
|
||||||
if (this.match(TokenType.ELSE)) {
|
|
||||||
elseBody = this.parseScriptBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = this.previous().endIndex;
|
|
||||||
|
|
||||||
return ASTNodeFactory.createIfStatement(condition, ifBody, elseIfClauses, elseBody, start, end, line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parsePipeline(): PipelineAst {
|
|
||||||
const start = this.current().startIndex;
|
|
||||||
const line = this.current().line;
|
|
||||||
const column = this.current().column;
|
|
||||||
|
|
||||||
const elements: PipelineElementAst[] = [];
|
|
||||||
|
|
||||||
// 解析第一个元素
|
|
||||||
const firstElement = this.parsePipelineElement();
|
|
||||||
elements.push(firstElement);
|
|
||||||
|
|
||||||
// 解析管道链
|
|
||||||
while (this.match(TokenType.PIPE)) {
|
|
||||||
const element = this.parsePipelineElement();
|
|
||||||
elements.push(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = this.previous().endIndex;
|
|
||||||
|
|
||||||
return ASTNodeFactory.createPipeline(elements, start, end, line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parsePipelineElement(): PipelineElementAst {
|
|
||||||
const start = this.current().startIndex;
|
|
||||||
const line = this.current().line;
|
|
||||||
const column = this.current().column;
|
|
||||||
|
|
||||||
const expression = this.parseAssignment();
|
|
||||||
const end = this.previous().endIndex;
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'PipelineElement',
|
|
||||||
expression,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseAssignment(): ExpressionAst {
|
|
||||||
const expr = this.parseLogicalOr();
|
|
||||||
|
|
||||||
if (this.match(TokenType.ASSIGNMENT)) {
|
|
||||||
const operator = this.previous().value;
|
|
||||||
const right = this.parseAssignment();
|
|
||||||
|
|
||||||
return ASTNodeFactory.createAssignment(
|
|
||||||
expr,
|
|
||||||
operator,
|
|
||||||
right,
|
|
||||||
expr.start,
|
|
||||||
right.end,
|
|
||||||
expr.line,
|
|
||||||
expr.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseLogicalOr(): ExpressionAst {
|
|
||||||
let expr = this.parseLogicalAnd();
|
|
||||||
|
|
||||||
while (this.match(TokenType.LOGICAL)) {
|
|
||||||
const operator = this.previous().value.toLowerCase();
|
|
||||||
if (operator === '-or' || operator === '-xor') {
|
|
||||||
const right = this.parseLogicalAnd();
|
|
||||||
expr = ASTNodeFactory.createBinaryExpression(
|
|
||||||
expr,
|
|
||||||
this.previous().value, // 使用原始大小写
|
|
||||||
right,
|
|
||||||
expr.start,
|
|
||||||
right.end,
|
|
||||||
expr.line,
|
|
||||||
expr.column
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 如果不是预期的操作符,回退
|
|
||||||
this.currentIndex--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseLogicalAnd(): ExpressionAst {
|
|
||||||
let expr = this.parseComparison();
|
|
||||||
|
|
||||||
while (this.match(TokenType.LOGICAL)) {
|
|
||||||
const operator = this.previous().value.toLowerCase();
|
|
||||||
if (operator === '-and') {
|
|
||||||
const right = this.parseComparison();
|
|
||||||
expr = ASTNodeFactory.createBinaryExpression(
|
|
||||||
expr,
|
|
||||||
this.previous().value, // 使用原始大小写
|
|
||||||
right,
|
|
||||||
expr.start,
|
|
||||||
right.end,
|
|
||||||
expr.line,
|
|
||||||
expr.column
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 如果不是预期的操作符,回退
|
|
||||||
this.currentIndex--;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseComparison(): ExpressionAst {
|
|
||||||
let expr = this.parseArithmetic();
|
|
||||||
|
|
||||||
while (this.match(TokenType.COMPARISON)) {
|
|
||||||
const operator = this.previous().value;
|
|
||||||
const right = this.parseArithmetic();
|
|
||||||
expr = ASTNodeFactory.createBinaryExpression(
|
|
||||||
expr,
|
|
||||||
operator,
|
|
||||||
right,
|
|
||||||
expr.start,
|
|
||||||
right.end,
|
|
||||||
expr.line,
|
|
||||||
expr.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseArithmetic(): ExpressionAst {
|
|
||||||
let expr = this.parseMultiplicative();
|
|
||||||
|
|
||||||
while (this.match(TokenType.ARITHMETIC)) {
|
|
||||||
const token = this.previous();
|
|
||||||
if (token.value === '+' || token.value === '-') {
|
|
||||||
const operator = token.value;
|
|
||||||
const right = this.parseMultiplicative();
|
|
||||||
expr = ASTNodeFactory.createBinaryExpression(
|
|
||||||
expr,
|
|
||||||
operator,
|
|
||||||
right,
|
|
||||||
expr.start,
|
|
||||||
right.end,
|
|
||||||
expr.line,
|
|
||||||
expr.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseMultiplicative(): ExpressionAst {
|
|
||||||
let expr = this.parseUnary();
|
|
||||||
|
|
||||||
while (this.match(TokenType.ARITHMETIC)) {
|
|
||||||
const token = this.previous();
|
|
||||||
if (token.value === '*' || token.value === '/' || token.value === '%') {
|
|
||||||
const operator = token.value;
|
|
||||||
const right = this.parseUnary();
|
|
||||||
expr = ASTNodeFactory.createBinaryExpression(
|
|
||||||
expr,
|
|
||||||
operator,
|
|
||||||
right,
|
|
||||||
expr.start,
|
|
||||||
right.end,
|
|
||||||
expr.line,
|
|
||||||
expr.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseUnary(): ExpressionAst {
|
|
||||||
if (this.match(TokenType.LOGICAL)) {
|
|
||||||
const token = this.previous();
|
|
||||||
const operator = token.value.toLowerCase();
|
|
||||||
if (operator === '-not') {
|
|
||||||
const operand = this.parseUnary();
|
|
||||||
return {
|
|
||||||
type: 'UnaryExpression',
|
|
||||||
operator: token.value, // 使用原始大小写
|
|
||||||
operand,
|
|
||||||
start: token.startIndex,
|
|
||||||
end: operand.end,
|
|
||||||
line: token.line,
|
|
||||||
column: token.column
|
|
||||||
} as UnaryExpressionAst;
|
|
||||||
} else {
|
|
||||||
// 如果不是-not,回退token
|
|
||||||
this.currentIndex--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理算术一元操作符(+, -)
|
|
||||||
if (this.match(TokenType.ARITHMETIC)) {
|
|
||||||
const token = this.previous();
|
|
||||||
if (token.value === '+' || token.value === '-') {
|
|
||||||
const operand = this.parseUnary();
|
|
||||||
return {
|
|
||||||
type: 'UnaryExpression',
|
|
||||||
operator: token.value,
|
|
||||||
operand,
|
|
||||||
start: token.startIndex,
|
|
||||||
end: operand.end,
|
|
||||||
line: token.line,
|
|
||||||
column: token.column
|
|
||||||
} as UnaryExpressionAst;
|
|
||||||
} else {
|
|
||||||
// 如果不是一元操作符,回退
|
|
||||||
this.currentIndex--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.parsePrimary();
|
|
||||||
}
|
|
||||||
|
|
||||||
private parsePrimary(): ExpressionAst {
|
|
||||||
// 变量
|
|
||||||
if (this.match(TokenType.VARIABLE)) {
|
|
||||||
const token = this.previous();
|
|
||||||
return ASTNodeFactory.createVariable(
|
|
||||||
token.value,
|
|
||||||
token.startIndex,
|
|
||||||
token.endIndex,
|
|
||||||
token.line,
|
|
||||||
token.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 字符串字面量
|
|
||||||
if (this.match(TokenType.STRING, TokenType.HERE_STRING)) {
|
|
||||||
const token = this.previous();
|
|
||||||
return ASTNodeFactory.createLiteral(
|
|
||||||
token.value,
|
|
||||||
'String',
|
|
||||||
token.startIndex,
|
|
||||||
token.endIndex,
|
|
||||||
token.line,
|
|
||||||
token.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数字字面量
|
|
||||||
if (this.match(TokenType.NUMBER)) {
|
|
||||||
const token = this.previous();
|
|
||||||
const value = parseFloat(token.value);
|
|
||||||
return ASTNodeFactory.createLiteral(
|
|
||||||
value,
|
|
||||||
'Number',
|
|
||||||
token.startIndex,
|
|
||||||
token.endIndex,
|
|
||||||
token.line,
|
|
||||||
token.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 命令调用 - 扩展支持更多token类型
|
|
||||||
if (this.match(TokenType.CMDLET, TokenType.IDENTIFIER)) {
|
|
||||||
return this.parseCommand();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理看起来像cmdlet但被错误标记的标识符
|
|
||||||
if (this.check(TokenType.IDENTIFIER) && this.current().value.includes('-')) {
|
|
||||||
this.advance();
|
|
||||||
return this.parseCommand();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 哈希表 @{...}
|
|
||||||
if (this.check(TokenType.LEFT_BRACE) && this.current().value === '@{') {
|
|
||||||
return this.parseHashtable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 脚本块表达式 {...} - 已在parseHashtableValue中处理
|
|
||||||
// 这里不需要处理,因为独立的脚本块很少见
|
|
||||||
|
|
||||||
// 括号表达式
|
|
||||||
if (this.match(TokenType.LEFT_PAREN)) {
|
|
||||||
const expr = this.parseExpression();
|
|
||||||
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after expression");
|
|
||||||
return {
|
|
||||||
type: 'ParenthesizedExpression',
|
|
||||||
expression: expr,
|
|
||||||
start: this.previous().startIndex,
|
|
||||||
end: this.previous().endIndex,
|
|
||||||
line: this.previous().line,
|
|
||||||
column: this.previous().column
|
|
||||||
} as ParenthesizedExpressionAst;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对于不认识的token,作为普通标识符处理而不是抛出异常
|
|
||||||
const token = this.advance();
|
|
||||||
return ASTNodeFactory.createLiteral(
|
|
||||||
token.value,
|
|
||||||
'String', // 将未识别的token作为字符串处理
|
|
||||||
token.startIndex,
|
|
||||||
token.endIndex,
|
|
||||||
token.line,
|
|
||||||
token.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseCommand(): CommandAst {
|
|
||||||
const start = this.previous().startIndex;
|
|
||||||
const line = this.previous().line;
|
|
||||||
const column = this.previous().column;
|
|
||||||
const commandName = this.previous().value;
|
|
||||||
|
|
||||||
const parameters: ParameterAst[] = [];
|
|
||||||
const args: ExpressionAst[] = [];
|
|
||||||
|
|
||||||
// 解析参数和参数值
|
|
||||||
while (!this.isAtEnd() &&
|
|
||||||
!this.check(TokenType.PIPE) &&
|
|
||||||
!this.check(TokenType.NEWLINE) &&
|
|
||||||
!this.check(TokenType.SEMICOLON) &&
|
|
||||||
!this.check(TokenType.RIGHT_PAREN) &&
|
|
||||||
!this.check(TokenType.RIGHT_BRACE)) {
|
|
||||||
|
|
||||||
if (this.match(TokenType.PARAMETER)) {
|
|
||||||
const paramToken = this.previous();
|
|
||||||
const param: ParameterAst = {
|
|
||||||
type: 'Parameter',
|
|
||||||
name: paramToken.value,
|
|
||||||
start: paramToken.startIndex,
|
|
||||||
end: paramToken.endIndex,
|
|
||||||
line: paramToken.line,
|
|
||||||
column: paramToken.column
|
|
||||||
};
|
|
||||||
|
|
||||||
// 检查参数是否有值
|
|
||||||
if (!this.check(TokenType.PARAMETER) &&
|
|
||||||
!this.check(TokenType.PIPE) &&
|
|
||||||
!this.check(TokenType.NEWLINE) &&
|
|
||||||
!this.check(TokenType.SEMICOLON)) {
|
|
||||||
param.value = this.parsePrimary();
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters.push(param);
|
|
||||||
} else {
|
|
||||||
// 位置参数
|
|
||||||
const arg = this.parsePrimary();
|
|
||||||
args.push(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const end = this.previous().endIndex;
|
|
||||||
|
|
||||||
return ASTNodeFactory.createCommand(commandName, parameters, args, start, end, line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseParameter(): ParameterAst | null {
|
|
||||||
if (this.match(TokenType.PARAMETER)) {
|
|
||||||
const token = this.previous();
|
|
||||||
const param: ParameterAst = {
|
|
||||||
type: 'Parameter',
|
|
||||||
name: token.value,
|
|
||||||
start: token.startIndex,
|
|
||||||
end: token.endIndex,
|
|
||||||
line: token.line,
|
|
||||||
column: token.column
|
|
||||||
};
|
|
||||||
|
|
||||||
// 检查是否有参数值
|
|
||||||
if (this.match(TokenType.ASSIGNMENT)) {
|
|
||||||
param.value = this.parseExpression();
|
|
||||||
}
|
|
||||||
|
|
||||||
return param;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseScriptBlock(): ScriptBlockAst {
|
|
||||||
const start = this.current().startIndex;
|
|
||||||
const line = this.current().line;
|
|
||||||
const column = this.current().column;
|
|
||||||
|
|
||||||
this.consume(TokenType.LEFT_BRACE, "Expected '{'");
|
|
||||||
|
|
||||||
const statements: StatementAst[] = [];
|
|
||||||
|
|
||||||
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
|
|
||||||
this.skipWhitespaceAndNewlines();
|
|
||||||
|
|
||||||
if (this.check(TokenType.RIGHT_BRACE)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const statement = this.parseStatement();
|
|
||||||
if (statement) {
|
|
||||||
statements.push(statement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.consume(TokenType.RIGHT_BRACE, "Expected '}'");
|
|
||||||
|
|
||||||
const end = this.previous().endIndex;
|
|
||||||
|
|
||||||
return ASTNodeFactory.createScriptBlock(statements, start, end, line, column);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseExpression(): ExpressionAst {
|
|
||||||
return this.parseAssignment();
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseComment(): CommentAst {
|
|
||||||
const token = this.previous();
|
|
||||||
const isMultiline = token.type === TokenType.MULTILINE_COMMENT;
|
|
||||||
|
|
||||||
return ASTNodeFactory.createComment(
|
|
||||||
token.value,
|
|
||||||
isMultiline,
|
|
||||||
token.startIndex,
|
|
||||||
token.endIndex,
|
|
||||||
token.line,
|
|
||||||
token.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助方法
|
|
||||||
private match(...types: TokenType[]): boolean {
|
|
||||||
for (const type of types) {
|
|
||||||
if (this.check(type)) {
|
|
||||||
this.advance();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private check(type: TokenType): boolean {
|
|
||||||
if (this.isAtEnd()) return false;
|
|
||||||
return this.current().type === type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private advance(): Token {
|
|
||||||
if (!this.isAtEnd()) this.currentIndex++;
|
|
||||||
return this.previous();
|
|
||||||
}
|
|
||||||
|
|
||||||
private isAtEnd(): boolean {
|
|
||||||
return this.currentIndex >= this.tokens.length || this.current().type === TokenType.EOF;
|
|
||||||
}
|
|
||||||
|
|
||||||
private current(): Token {
|
|
||||||
if (this.currentIndex >= this.tokens.length) {
|
|
||||||
return this.tokens[this.tokens.length - 1];
|
|
||||||
}
|
|
||||||
return this.tokens[this.currentIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
private previous(): Token {
|
|
||||||
return this.tokens[this.currentIndex - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
private consume(type: TokenType, message: string): Token {
|
|
||||||
if (this.check(type)) return this.advance();
|
|
||||||
|
|
||||||
const current = this.current();
|
|
||||||
throw new Error(`${message}. Got ${current.type}(${current.value}) at line ${current.line}, column ${current.column}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private parseHashtable(): ExpressionAst {
|
|
||||||
const start = this.current().startIndex;
|
|
||||||
const line = this.current().line;
|
|
||||||
const column = this.current().column;
|
|
||||||
|
|
||||||
// 消费 @{
|
|
||||||
this.advance();
|
|
||||||
|
|
||||||
const entries: any[] = [];
|
|
||||||
|
|
||||||
// 解析哈希表内容
|
|
||||||
if (!this.check(TokenType.RIGHT_BRACE)) {
|
|
||||||
do {
|
|
||||||
// 解析键 - 只接受简单的标识符或字符串
|
|
||||||
const key = this.parseHashtableKey();
|
|
||||||
|
|
||||||
// 消费 =
|
|
||||||
this.consume(TokenType.ASSIGNMENT, "Expected '=' after hashtable key");
|
|
||||||
|
|
||||||
// 解析值
|
|
||||||
const value = this.parseHashtableValue();
|
|
||||||
|
|
||||||
entries.push({
|
|
||||||
type: 'HashtableEntry',
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
start: key.start,
|
|
||||||
end: value.end,
|
|
||||||
line: key.line,
|
|
||||||
column: key.column
|
|
||||||
});
|
|
||||||
|
|
||||||
} while (this.match(TokenType.SEMICOLON));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after hashtable entries");
|
|
||||||
const end = this.previous().endIndex;
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'Hashtable',
|
|
||||||
entries,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
} as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseHashtableKey(): ExpressionAst {
|
|
||||||
// 哈希表键只能是简单的标识符或字符串
|
|
||||||
if (this.match(TokenType.STRING, TokenType.HERE_STRING)) {
|
|
||||||
const token = this.previous();
|
|
||||||
return ASTNodeFactory.createLiteral(
|
|
||||||
token.value,
|
|
||||||
'String',
|
|
||||||
token.startIndex,
|
|
||||||
token.endIndex,
|
|
||||||
token.line,
|
|
||||||
token.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 接受各种可能的标识符类型作为哈希表键
|
|
||||||
if (this.match(TokenType.IDENTIFIER, TokenType.CMDLET, TokenType.KEYWORD)) {
|
|
||||||
const token = this.previous();
|
|
||||||
return ASTNodeFactory.createLiteral(
|
|
||||||
token.value,
|
|
||||||
'String',
|
|
||||||
token.startIndex,
|
|
||||||
token.endIndex,
|
|
||||||
token.line,
|
|
||||||
token.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对于任何其他类型的token,尝试作为字面量处理
|
|
||||||
const currentToken = this.current();
|
|
||||||
this.advance();
|
|
||||||
return ASTNodeFactory.createLiteral(
|
|
||||||
currentToken.value,
|
|
||||||
'String',
|
|
||||||
currentToken.startIndex,
|
|
||||||
currentToken.endIndex,
|
|
||||||
currentToken.line,
|
|
||||||
currentToken.column
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseHashtableValue(): ExpressionAst {
|
|
||||||
// 哈希表值可以是任何表达式
|
|
||||||
if (this.check(TokenType.LEFT_BRACE)) {
|
|
||||||
// 这是一个脚本块 {expression} - 完全绕过复杂解析
|
|
||||||
const start = this.current().startIndex;
|
|
||||||
const line = this.current().line;
|
|
||||||
const column = this.current().column;
|
|
||||||
|
|
||||||
// 直接从原始代码中提取脚本块内容
|
|
||||||
const startPos = this.current().startIndex;
|
|
||||||
this.advance(); // 消费 {
|
|
||||||
|
|
||||||
let braceLevel = 1;
|
|
||||||
let endPos = this.current().startIndex;
|
|
||||||
|
|
||||||
// 找到匹配的右大括号位置
|
|
||||||
while (!this.isAtEnd() && braceLevel > 0) {
|
|
||||||
const token = this.current();
|
|
||||||
if (token.type === TokenType.LEFT_BRACE) {
|
|
||||||
braceLevel++;
|
|
||||||
} else if (token.type === TokenType.RIGHT_BRACE) {
|
|
||||||
braceLevel--;
|
|
||||||
if (braceLevel === 0) {
|
|
||||||
endPos = token.startIndex;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after script block");
|
|
||||||
const end = this.previous().endIndex;
|
|
||||||
|
|
||||||
// 从原始代码中提取内容(从 { 后到 } 前)
|
|
||||||
const rawContent = this.getOriginalCodeSlice(startPos + 1, endPos);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: 'ScriptBlockExpression',
|
|
||||||
rawContent: rawContent.trim(), // 去掉首尾空白
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
line,
|
|
||||||
column
|
|
||||||
} as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对于其他值,使用简单的解析
|
|
||||||
return this.parsePrimary();
|
|
||||||
}
|
|
||||||
|
|
||||||
private getOriginalCodeSlice(start: number, end: number): string {
|
|
||||||
// 直接从原始代码中提取片段
|
|
||||||
if (this.originalCode) {
|
|
||||||
return this.originalCode.substring(start, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 回退到基于token重建(如果没有原始代码)
|
|
||||||
let result = '';
|
|
||||||
for (const token of this.tokens) {
|
|
||||||
if (token.startIndex >= start && token.endIndex <= end) {
|
|
||||||
result += token.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private skipWhitespaceAndNewlines(): void {
|
|
||||||
while (this.match(TokenType.WHITESPACE, TokenType.NEWLINE)) {
|
|
||||||
// 继续跳过
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -46,7 +46,6 @@ import * as shellPrettierPlugin from "@/common/prettier/plugins/shell";
|
|||||||
import tomlPrettierPlugin from "@/common/prettier/plugins/toml";
|
import tomlPrettierPlugin from "@/common/prettier/plugins/toml";
|
||||||
import clojurePrettierPlugin from "@cospaia/prettier-plugin-clojure";
|
import clojurePrettierPlugin from "@cospaia/prettier-plugin-clojure";
|
||||||
import groovyPrettierPlugin from "@/common/prettier/plugins/groovy";
|
import groovyPrettierPlugin from "@/common/prettier/plugins/groovy";
|
||||||
import powershellPrettierPlugin from "@/common/prettier/plugins/powershell";
|
|
||||||
import scalaPrettierPlugin from "@/common/prettier/plugins/scala";
|
import scalaPrettierPlugin from "@/common/prettier/plugins/scala";
|
||||||
import * as prettierPluginEstree from "prettier/plugins/estree";
|
import * as prettierPluginEstree from "prettier/plugins/estree";
|
||||||
|
|
||||||
@@ -146,10 +145,7 @@ export const LANGUAGES: LanguageInfo[] = [
|
|||||||
parser: "groovy",
|
parser: "groovy",
|
||||||
plugins: [groovyPrettierPlugin]
|
plugins: [groovyPrettierPlugin]
|
||||||
}),
|
}),
|
||||||
new LanguageInfo("ps1", "PowerShell", StreamLanguage.define(powerShell).parser,{
|
new LanguageInfo("ps1", "PowerShell", StreamLanguage.define(powerShell).parser),
|
||||||
parser: "powershell",
|
|
||||||
plugins: [powershellPrettierPlugin]
|
|
||||||
}),
|
|
||||||
new LanguageInfo("dart", "Dart", null), // 暂无解析器
|
new LanguageInfo("dart", "Dart", null), // 暂无解析器
|
||||||
new LanguageInfo("scala", "Scala", StreamLanguage.define(scala).parser,{
|
new LanguageInfo("scala", "Scala", StreamLanguage.define(scala).parser,{
|
||||||
parser: "scala",
|
parser: "scala",
|
||||||
|
|||||||
Reference in New Issue
Block a user