blob: e9e13f5cab3071dff5c825e12b3076054dda690b [file] [log] [blame]
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as ts from 'typescript';
import * as codeUtils from '../utils/codeUtils';
import {LegacyLifecycleMethodName, OrdinaryMethods} from './polymerComponentParser';
interface LegacyLifecycleMethodContent {
codeAtMethodStart: ts.Statement[];
existingMethod?: ts.MethodDeclaration;
codeAtMethodEnd: ts.Statement[];
}
export interface LifecycleMethod {
originalPos: number;//-1 - no original method exists
method: ts.MethodDeclaration;
name: LegacyLifecycleMethodName;
}
export class LifecycleMethodsBuilder {
private readonly methods: Map<LegacyLifecycleMethodName, LegacyLifecycleMethodContent> = new Map();
private getMethodContent(name: LegacyLifecycleMethodName): LegacyLifecycleMethodContent {
if(!this.methods.has(name)) {
this.methods.set(name, {
codeAtMethodStart: [],
codeAtMethodEnd: []
});
}
return this.methods.get(name)!;
}
public addListeners(legacyListeners: ts.ObjectLiteralExpression, legacyOrdinaryMethods: OrdinaryMethods) {
for(const listener of legacyListeners.properties) {
const propertyAssignment = codeUtils.assertNodeKind(listener, ts.SyntaxKind.PropertyAssignment) as ts.PropertyAssignment;
if(!propertyAssignment.name) {
throw new Error("Listener must have event name");
}
let eventNameLiteral: ts.StringLiteral;
let commentsToRestore: string[] = [];
if(propertyAssignment.name.kind === ts.SyntaxKind.StringLiteral) {
//We don't loose comment in this case, because we keep literal as is
eventNameLiteral = propertyAssignment.name;
} else if(propertyAssignment.name.kind === ts.SyntaxKind.Identifier) {
eventNameLiteral = ts.createStringLiteral(propertyAssignment.name.text);
commentsToRestore = codeUtils.getLeadingComments(propertyAssignment);
} else {
throw new Error(`Unsupported type ${ts.SyntaxKind[propertyAssignment.name.kind]}`);
}
const handlerLiteral = codeUtils.assertNodeKind(propertyAssignment.initializer, ts.SyntaxKind.StringLiteral) as ts.StringLiteral;
const handlerImpl = legacyOrdinaryMethods.get(handlerLiteral.text);
if(!handlerImpl) {
throw new Error(`Can't find event handler '${handlerLiteral.text}'`);
}
const eventHandlerAccess = ts.createPropertyAccess(ts.createThis(), handlerLiteral.text);
//ts.forEachChild(handler)
const args: ts.Identifier[] = handlerImpl.parameters.map((arg) => codeUtils.assertNodeKind(arg.name, ts.SyntaxKind.Identifier));
const eventHandlerCall = ts.createCall(eventHandlerAccess, [], args);
let arrowFunc = ts.createArrowFunction([], [], handlerImpl.parameters, undefined, undefined, eventHandlerCall);
arrowFunc = codeUtils.addNewLineBeforeNode(arrowFunc);
const methodContent = this.getMethodContent("created");
//See https://polymer-library.polymer-project.org/3.0/docs/devguide/gesture-events for a list of events
if(["down", "up", "tap", "track"].indexOf(eventNameLiteral.text) >= 0) {
const methodCall = ts.createCall(codeUtils.createNameExpression("Polymer.Gestures.addListener"), [], [ts.createThis(), eventNameLiteral, arrowFunc]);
methodContent.codeAtMethodEnd.push(ts.createExpressionStatement(methodCall));
}
else {
let methodCall = ts.createCall(ts.createPropertyAccess(ts.createThis(), "addEventListener"), [], [eventNameLiteral, arrowFunc]);
methodCall = codeUtils.restoreLeadingComments(methodCall, commentsToRestore);
methodContent.codeAtMethodEnd.push(ts.createExpressionStatement(methodCall));
}
}
}
public addHostAttributes(legacyHostAttributes: ts.ObjectLiteralExpression) {
for(const listener of legacyHostAttributes.properties) {
const propertyAssignment = codeUtils.assertNodeKind(listener, ts.SyntaxKind.PropertyAssignment) as ts.PropertyAssignment;
if(!propertyAssignment.name) {
throw new Error("Listener must have event name");
}
let attributeNameLiteral: ts.StringLiteral;
if(propertyAssignment.name.kind === ts.SyntaxKind.StringLiteral) {
attributeNameLiteral = propertyAssignment.name;
} else if(propertyAssignment.name.kind === ts.SyntaxKind.Identifier) {
attributeNameLiteral = ts.createStringLiteral(propertyAssignment.name.text);
} else {
throw new Error(`Unsupported type ${ts.SyntaxKind[propertyAssignment.name.kind]}`);
}
let attributeValueLiteral: ts.StringLiteral | ts.NumericLiteral;
if(propertyAssignment.initializer.kind === ts.SyntaxKind.StringLiteral) {
attributeValueLiteral = propertyAssignment.initializer as ts.StringLiteral;
} else if(propertyAssignment.initializer.kind === ts.SyntaxKind.NumericLiteral) {
attributeValueLiteral = propertyAssignment.initializer as ts.NumericLiteral;
} else {
throw new Error(`Unsupported type ${ts.SyntaxKind[propertyAssignment.initializer.kind]}`);
}
const methodCall = ts.createCall(ts.createPropertyAccess(ts.createThis(), "_ensureAttribute"), [], [attributeNameLiteral, attributeValueLiteral]);
this.getMethodContent("ready").codeAtMethodEnd.push(ts.createExpressionStatement(methodCall));
}
}
public addLegacyLifecycleMethod(name: LegacyLifecycleMethodName, method: ts.MethodDeclaration) {
const content = this.getMethodContent(name);
if(content.existingMethod) {
throw new Error(`Legacy lifecycle method ${name} already added`);
}
content.existingMethod = method;
}
public buildNewMethods(): LifecycleMethod[] {
const result = [];
for(const [name, content] of this.methods) {
const newMethod = this.createLifecycleMethod(name, content.existingMethod, content.codeAtMethodStart, content.codeAtMethodEnd);
if(!newMethod) continue;
result.push({
name,
originalPos: content.existingMethod ? content.existingMethod.pos : -1,
method: newMethod
})
}
return result;
}
private createLifecycleMethod(name: string, methodDecl: ts.MethodDeclaration | undefined, codeAtStart: ts.Statement[], codeAtEnd: ts.Statement[]): ts.MethodDeclaration | undefined {
return codeUtils.createMethod(name, methodDecl, codeAtStart, codeAtEnd, true);
}
}