121 lines
4.3 KiB
TypeScript
121 lines
4.3 KiB
TypeScript
import { BaseProcessor, ProcessorContext, ProcessorResult } from './base-processor';
|
|
import { ComponentIdRegistry } from './component-id-registry';
|
|
|
|
const DECORATOR_MAP: Record<string, string> = {
|
|
'Component': '_quarcComponent',
|
|
'Directive': '_quarcDirective',
|
|
'Pipe': '_quarcPipe',
|
|
'Injectable': '_quarcInjectable',
|
|
};
|
|
|
|
export class ClassDecoratorProcessor extends BaseProcessor {
|
|
private componentIdRegistry = ComponentIdRegistry.getInstance();
|
|
|
|
get name(): string {
|
|
return 'class-decorator-processor';
|
|
}
|
|
|
|
async process(context: ProcessorContext): Promise<ProcessorResult> {
|
|
const decoratorNames = Object.keys(DECORATOR_MAP);
|
|
const hasDecorator = decoratorNames.some(d => context.source.includes(`@${d}`));
|
|
|
|
if (!hasDecorator) {
|
|
return this.noChange(context.source);
|
|
}
|
|
|
|
let source = context.source;
|
|
let modified = false;
|
|
|
|
for (const [decoratorName, propertyName] of Object.entries(DECORATOR_MAP)) {
|
|
const result = this.processDecorator(source, decoratorName, propertyName, context.filePath);
|
|
if (result.modified) {
|
|
source = result.source;
|
|
modified = true;
|
|
}
|
|
}
|
|
|
|
return modified ? this.changed(source) : this.noChange(source);
|
|
}
|
|
|
|
private processDecorator(
|
|
source: string,
|
|
decoratorName: string,
|
|
propertyName: string,
|
|
filePath: string,
|
|
): { source: string; modified: boolean } {
|
|
let result = source;
|
|
let modified = false;
|
|
let searchStart = 0;
|
|
|
|
while (true) {
|
|
const decoratorStart = result.indexOf(`@${decoratorName}(`, searchStart);
|
|
if (decoratorStart === -1) break;
|
|
|
|
const argsStart = decoratorStart + decoratorName.length + 2;
|
|
const argsEnd = this.findMatchingParen(result, argsStart - 1);
|
|
if (argsEnd === -1) {
|
|
searchStart = argsStart;
|
|
continue;
|
|
}
|
|
|
|
const decoratorArgs = result.substring(argsStart, argsEnd).trim();
|
|
|
|
const afterDecorator = result.substring(argsEnd + 1);
|
|
const classMatch = afterDecorator.match(/^\s*\n?\s*export\s+class\s+(\w+)/);
|
|
|
|
if (!classMatch) {
|
|
searchStart = argsEnd + 1;
|
|
continue;
|
|
}
|
|
|
|
const className = classMatch[1];
|
|
|
|
const afterClassName = result.substring(argsEnd + 1 + classMatch[0].length);
|
|
const classBodyMatch = afterClassName.match(/^(\s*(?:extends\s+\w+\s*)?(?:implements\s+[\w,\s]+)?\s*)\{/);
|
|
|
|
if (!classBodyMatch) {
|
|
searchStart = argsEnd + 1;
|
|
continue;
|
|
}
|
|
|
|
const fullMatchEnd = argsEnd + 1 + classMatch[0].length + classBodyMatch[0].length;
|
|
|
|
const staticProperty = `static ${propertyName} = [${decoratorArgs || '{}'}];`;
|
|
|
|
let additionalProperties = '';
|
|
if (decoratorName === 'Component') {
|
|
const scopeId = this.componentIdRegistry.getComponentId(filePath);
|
|
additionalProperties = `\n static _scopeId = '${scopeId}';\n static __quarc_original_name__ = '${className}';`;
|
|
} else if (decoratorName === 'Directive') {
|
|
additionalProperties = `\n static __quarc_original_name__ = '${className}';`;
|
|
} else if (decoratorName === 'Injectable') {
|
|
additionalProperties = `\n static __quarc_original_name__ = '${className}';`;
|
|
}
|
|
|
|
const classDeclaration = `export class ${className}${classBodyMatch[1]}{\n ${staticProperty}${additionalProperties}`;
|
|
|
|
result = result.slice(0, decoratorStart) + classDeclaration + result.slice(fullMatchEnd);
|
|
modified = true;
|
|
searchStart = decoratorStart + classDeclaration.length;
|
|
}
|
|
|
|
return { source: result, modified };
|
|
}
|
|
|
|
private findMatchingParen(source: string, startIndex: number): number {
|
|
if (source[startIndex] !== '(') return -1;
|
|
|
|
let depth = 1;
|
|
let i = startIndex + 1;
|
|
|
|
while (i < source.length && depth > 0) {
|
|
const char = source[i];
|
|
if (char === '(') depth++;
|
|
else if (char === ')') depth--;
|
|
i++;
|
|
}
|
|
|
|
return depth === 0 ? i - 1 : -1;
|
|
}
|
|
}
|