Files
developtools_ace-ets2bundle/compiler/src/validate_ui_syntax.ts
T
houhaoyu 3927426b57 houhaoyu@huawei.com
fix call extend attr in extend attr

Signed-off-by: houhaoyu <houhaoyu@huawei.com>
Change-Id: Ib615b0d0d3b4afae44798759f69eba68e9d146ce
2022-04-24 19:18:40 +08:00

860 lines
34 KiB
TypeScript

/*
* Copyright (c) 2021 Huawei Device Co., Ltd.
* 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 ts from 'typescript';
import path from 'path';
import {
INNER_COMPONENT_DECORATORS,
COMPONENT_DECORATOR_ENTRY,
COMPONENT_DECORATOR_PREVIEW,
COMPONENT_DECORATOR_COMPONENT,
COMPONENT_DECORATOR_CUSTOM_DIALOG,
NATIVE_MODULE,
SYSTEM_PLUGIN,
OHOS_PLUGIN,
INNER_COMPONENT_MEMBER_DECORATORS,
COMPONENT_FOREACH,
COMPONENT_LAZYFOREACH,
COMPONENT_STATE_DECORATOR,
COMPONENT_LINK_DECORATOR,
COMPONENT_PROP_DECORATOR,
COMPONENT_STORAGE_PROP_DECORATOR,
COMPONENT_STORAGE_LINK_DECORATOR,
COMPONENT_PROVIDE_DECORATOR,
COMPONENT_CONSUME_DECORATOR,
COMPONENT_OBJECT_LINK_DECORATOR,
COMPONENT_OBSERVED_DECORATOR,
COMPONENT_LOCAL_STORAGE_LINK_DECORATOR,
COMPONENT_LOCAL_STORAGE_PROP_DECORATOR,
STYLES,
VALIDATE_MODULE,
COMPONENT_BUILDER_DECORATOR
} from './pre_define';
import {
INNER_COMPONENT_NAMES,
AUTOMIC_COMPONENT,
SINGLE_CHILD_COMPONENT,
SPECIFIC_CHILD_COMPONENT,
BUILDIN_STYLE_NAMES,
EXTEND_ATTRIBUTE,
GLOBAL_STYLE_FUNCTION,
STYLES_ATTRIBUTE,
CUSTOM_BUILDER_METHOD
} from './component_map';
import {
LogType,
LogInfo,
componentInfo,
addLog,
hasDecorator
} from './utils';
import { projectConfig } from '../main';
import { collectExtend } from './process_ui_syntax';
import { importModuleCollection } from './ets_checker';
import { isExtendFunction } from './process_ui_syntax';
export interface ComponentCollection {
localStorageName: string;
entryComponentPos: number;
entryComponent: string;
previewComponent: string;
customDialogs: Set<string>;
customComponents: Set<string>;
currentClassName: string;
}
export interface IComponentSet {
properties: Set<string>;
regulars: Set<string>;
states: Set<string>;
links: Set<string>;
props: Set<string>;
storageProps: Set<string>;
storageLinks: Set<string>;
provides: Set<string>;
consumes: Set<string>;
objectLinks: Set<string>;
localStorageLink: Map<string, Set<string>>;
localStorageProp: Map<string, Set<string>>;
}
export const componentCollection: ComponentCollection = {
localStorageName: null,
entryComponentPos: null,
entryComponent: null,
previewComponent: null,
customDialogs: new Set([]),
customComponents: new Set([]),
currentClassName: null
};
export const observedClassCollection: Set<string> = new Set();
export const enumCollection: Set<string> = new Set();
export const classMethodCollection: Map<string, Set<string>> = new Map();
export const dollarCollection: Set<string> = new Set();
export const propertyCollection: Map<string, Set<string>> = new Map();
export const stateCollection: Map<string, Set<string>> = new Map();
export const linkCollection: Map<string, Set<string>> = new Map();
export const propCollection: Map<string, Set<string>> = new Map();
export const regularCollection: Map<string, Set<string>> = new Map();
export const storagePropCollection: Map<string, Set<string>> = new Map();
export const storageLinkCollection: Map<string, Set<string>> = new Map();
export const provideCollection: Map<string, Set<string>> = new Map();
export const consumeCollection: Map<string, Set<string>> = new Map();
export const objectLinkCollection: Map<string, Set<string>> = new Map();
export const localStorageLinkCollection: Map<string, Map<string, Set<string>>> = new Map();
export const localStoragePropCollection: Map<string, Map<string, Set<string>>> = new Map();
export const isStaticViewCollection: Map<string, boolean> = new Map();
export const moduleCollection: Set<string> = new Set();
export const useOSFiles: Set<string> = new Set();
export function validateUISyntax(source: string, content: string, filePath: string,
fileQuery: string): LogInfo[] {
let log: LogInfo[] = [];
if (path.basename(filePath) !== 'app.ets') {
const res: LogInfo[] = checkComponentDecorator(source, filePath, fileQuery);
if (res) {
log = log.concat(res);
}
const allComponentNames: Set<string> =
new Set([...INNER_COMPONENT_NAMES, ...componentCollection.customComponents]);
checkUISyntax(filePath, allComponentNames, content, log);
componentCollection.customComponents.forEach(item => componentInfo.componentNames.add(item));
}
return log;
}
function checkComponentDecorator(source: string, filePath: string,
fileQuery: string): LogInfo[] | null {
const log: LogInfo[] = [];
const sourceFile: ts.SourceFile = ts.createSourceFile(filePath, source,
ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS);
if (sourceFile && sourceFile.statements && sourceFile.statements.length) {
const result: DecoratorResult = {
entryCount: 0,
previewCount: 0
};
sourceFile.statements.forEach((item, index, arr) => {
if (isObservedClass(item)) {
// @ts-ignore
observedClassCollection.add(item.name.getText());
}
if (ts.isEnumDeclaration(item) && item.name) {
enumCollection.add(item.name.getText());
}
if (ts.isStructDeclaration(item)) {
if (item.name && ts.isIdentifier(item.name)) {
if (item.decorators && item.decorators.length) {
checkDecorators(item.decorators, result, item.name, log, sourceFile);
} else {
const message: string = `A struct should use decorator '@Component'.`;
addLog(LogType.WARN, message, item.getStart(), log, sourceFile);
}
} else {
const message: string = `A struct must have a name.`;
addLog(LogType.ERROR, message, item.getStart(), log, sourceFile);
}
}
if (ts.isMissingDeclaration(item)) {
const decorators: ts.NodeArray<ts.Decorator> = item.decorators;
for (let i = 0; i < decorators.length; i++) {
if (decorators[i] && /struct/.test(decorators[i].getText())) {
const message: string = `Please use a valid decorator.`;
addLog(LogType.ERROR, message, item.getStart(), log, sourceFile);
break;
}
}
}
if (ts.isFunctionDeclaration(item) && item.decorators && item.decorators.length === 1 &&
item.decorators[0].expression && item.decorators[0].expression.getText() === STYLES) {
STYLES_ATTRIBUTE.add(item.name.getText());
GLOBAL_STYLE_FUNCTION.set(item.name.getText(), item.body);
BUILDIN_STYLE_NAMES.add(item.name.getText());
}
});
validateEntryAndPreviewCount(result, fileQuery, sourceFile.fileName, projectConfig.isPreview,
!!projectConfig.checkEntry, log);
}
return log.length ? log : null;
}
function validateEntryAndPreviewCount(result: DecoratorResult, fileQuery: string,
fileName: string, isPreview: boolean, checkEntry: boolean, log: LogInfo[]): void {
if (result.previewCount > 10 && fileQuery === '?entry') {
log.push({
type: LogType.ERROR,
message: `A page can contain at most 10 '@Preview' decorators.`,
fileName: fileName
});
}
if (result.entryCount > 1 && fileQuery === '?entry') {
log.push({
type: LogType.ERROR,
message: `A page can't contain more than one '@Entry' decorator`,
fileName: fileName
});
}
if (isPreview && !checkEntry && result.previewCount < 1 && result.entryCount !== 1 &&
fileQuery === '?entry') {
log.push({
type: LogType.ERROR,
message: `A page configured in '${projectConfig.pagesJsonFileName}' must have one and only one '@Entry' `
+ `decorator, or at least one '@Preview' decorator.`,
fileName: fileName
});
} else if ((!isPreview || isPreview && checkEntry) && result.entryCount !== 1 && fileQuery === '?entry') {
log.push({
type: LogType.ERROR,
message: `A page configured in '${projectConfig.pagesJsonFileName}' must have one and only one '@Entry' `
+ `decorator.`,
fileName: fileName
});
}
}
export function isObservedClass(node: ts.Node): boolean {
if (ts.isClassDeclaration(node) && hasDecorator(node, COMPONENT_OBSERVED_DECORATOR)) {
return true;
}
return false;
}
export function isCustomDialogClass(node: ts.Node): boolean {
if (ts.isClassDeclaration(node) && hasDecorator(node, COMPONENT_DECORATOR_CUSTOM_DIALOG)) {
return true;
}
return false;
}
interface DecoratorResult {
entryCount: number;
previewCount: number;
}
function checkDecorators(decorators: ts.NodeArray<ts.Decorator>, result: DecoratorResult,
component: ts.Identifier, log: LogInfo[], sourceFile: ts.SourceFile): void {
let hasComponentDecorator: boolean = false;
const componentName: string = component.getText();
decorators.forEach((element) => {
const name: string = element.getText().replace(/\([^\(\)]*\)/, '').trim();
if (INNER_COMPONENT_DECORATORS.has(name)) {
componentCollection.customComponents.add(componentName);
switch (name) {
case COMPONENT_DECORATOR_ENTRY:
result.entryCount++;
componentCollection.entryComponent = componentName;
collectLocalStorageName(element);
break;
case COMPONENT_DECORATOR_PREVIEW:
result.previewCount++;
componentCollection.previewComponent = componentName;
break;
case COMPONENT_DECORATOR_COMPONENT:
hasComponentDecorator = true;
break;
case COMPONENT_DECORATOR_CUSTOM_DIALOG:
componentCollection.customDialogs.add(componentName);
hasComponentDecorator = true;
break;
}
} else {
const pos: number = element.expression ? element.expression.pos : element.pos;
const message: string = `The struct '${componentName}' use invalid decorator.`;
addLog(LogType.WARN, message, pos, log, sourceFile);
}
});
if (!hasComponentDecorator) {
const message: string = `The struct '${componentName}' should use decorator '@Component'.`;
addLog(LogType.WARN, message, component.pos, log, sourceFile);
}
if (BUILDIN_STYLE_NAMES.has(componentName)) {
const message: string = `The struct '${componentName}' cannot have the same name ` +
`as the built-in attribute '${componentName}'.`;
addLog(LogType.ERROR, message, component.pos, log, sourceFile);
}
if (INNER_COMPONENT_NAMES.has(componentName)) {
const message: string = `The struct '${componentName}' cannot have the same name ` +
`as the built-in component '${componentName}'.`;
addLog(LogType.ERROR, message, component.pos, log, sourceFile);
}
}
function collectLocalStorageName(node: ts.Decorator): void {
if (node && node.expression && ts.isCallExpression(node.expression)) {
componentCollection.entryComponentPos = node.expression.pos;
if (node.expression.arguments && node.expression.arguments.length) {
node.expression.arguments.forEach((item: ts.Node, index: number) => {
if (ts.isIdentifier(item) && index === 0) {
componentCollection.localStorageName = item.getText();
}
});
}
} else {
componentCollection.localStorageName = null;
}
}
function checkUISyntax(filePath: string, allComponentNames: Set<string>, content: string,
log: LogInfo[]): void {
const sourceFile: ts.SourceFile = ts.createSourceFile(filePath, content,
ts.ScriptTarget.Latest, true, ts.ScriptKind.ETS);
visitAllNode(sourceFile, sourceFile, allComponentNames, log);
}
function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponentNames: Set<string>,
log: LogInfo[]) {
checkAllNode(node, allComponentNames, sourceFileNode, log);
if (ts.isStructDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
collectComponentProps(node);
}
if (ts.isMethodDeclaration(node) && hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) {
CUSTOM_BUILDER_METHOD.add(node.name.getText());
}
if (ts.isFunctionDeclaration(node) && isExtendFunction(node)) {
const componentName: string = isExtendFunction(node);
collectExtend(EXTEND_ATTRIBUTE, componentName, node.name.getText());
}
node.getChildren().forEach((item: ts.Node) => visitAllNode(item, sourceFileNode, allComponentNames, log));
}
function checkAllNode(node: ts.Node, allComponentNames: Set<string>, sourceFileNode: ts.SourceFile,
log: LogInfo[]): void {
if (ts.isExpressionStatement(node) && node.expression && ts.isIdentifier(node.expression) &&
allComponentNames.has(node.expression.escapedText.toString())) {
const pos: number = node.expression.getStart();
const message: string =
`The component name must be followed by parentheses, like '${node.expression.getText()}()'.`;
addLog(LogType.ERROR, message, pos, log, sourceFileNode);
}
checkNoChildComponent(node, sourceFileNode, log);
checkOneChildComponent(node, allComponentNames, sourceFileNode, log);
checkSpecificChildComponent(node, allComponentNames, sourceFileNode, log);
}
function checkNoChildComponent(node: ts.Node, sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
ts.isIdentifier(node.expression.expression) && hasChild(node)) {
const componentName: string = node.expression.expression.escapedText.toString();
const pos: number = node.expression.expression.getStart();
const message: string = `The component '${componentName}' can't have any child.`;
addLog(LogType.ERROR, message, pos, log, sourceFileNode);
}
}
function hasChild(node: ts.ExpressionStatement): boolean {
const etsComponentExpression: ts.EtsComponentExpression = node.expression as ts.EtsComponentExpression;
const nodeName: ts.Identifier = etsComponentExpression.expression as ts.Identifier;
if (AUTOMIC_COMPONENT.has(nodeName.escapedText.toString()) && getNextNode(etsComponentExpression)) {
return true;
}
return false;
}
function getNextNode(node: ts.EtsComponentExpression): ts.Block {
if (node.body && ts.isBlock(node.body)) {
const statementsArray: ts.Block = node.body;
return statementsArray;
}
}
function checkOneChildComponent(node: ts.Node, allComponentNames: Set<string>,
sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
ts.isIdentifier(node.expression.expression) && hasNonSingleChild(node, allComponentNames)) {
const componentName: string = node.expression.expression.escapedText.toString();
const pos: number = node.expression.expression.getStart();
const message: string =
`The component '${componentName}' can only have a single child component.`;
addLog(LogType.ERROR, message, pos, log, sourceFileNode);
}
}
function hasNonSingleChild(node: ts.ExpressionStatement, allComponentNames: Set<string>): boolean {
const etsComponentExpression: ts.EtsComponentExpression = node.expression as ts.EtsComponentExpression;
const nodeName: ts.Identifier = etsComponentExpression.expression as ts.Identifier;
const BlockNode: ts.Block = getNextNode(etsComponentExpression);
if (SINGLE_CHILD_COMPONENT.has(nodeName.escapedText.toString())) {
if (!BlockNode) {
return false;
}
if (BlockNode && BlockNode.statements) {
const length: number = BlockNode.statements.length;
if (!length) {
return false;
}
if (length > 3) {
return true;
}
const childCount: number = getBlockChildrenCount(BlockNode, allComponentNames);
if (childCount > 1) {
return true;
}
}
}
return false;
}
function getBlockChildrenCount(blockNode: ts.Block, allComponentNames: Set<string>): number {
let maxCount: number = 0;
const length: number = blockNode.statements.length;
for (let i = 0; i < length; ++i) {
const item: ts.Node = blockNode.statements[i];
if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) &&
isForEachComponent(item.expression)) {
maxCount += 2;
}
if (ts.isIfStatement(item)) {
maxCount += getIfChildrenCount(item, allComponentNames);
}
if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression)) {
maxCount += 1;
}
if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression)) {
let newNode: any = item.expression;
while (newNode.expression) {
if (ts.isEtsComponentExpression(newNode) || ts.isCallExpression(newNode) &&
isComponent(newNode, allComponentNames)) {
maxCount += 1;
}
newNode = newNode.expression;
}
}
if (maxCount > 1) {
break;
}
}
return maxCount;
}
function isComponent(node: ts.EtsComponentExpression, allComponentNames: Set<string>): boolean {
if (ts.isIdentifier(node.expression) &&
allComponentNames.has(node.expression.escapedText.toString())) {
return true;
}
return false;
}
function isForEachComponent(node: ts.CallExpression): boolean {
if (ts.isIdentifier(node.expression)) {
const componentName: string = node.expression.escapedText.toString();
return componentName === COMPONENT_FOREACH || componentName === COMPONENT_LAZYFOREACH;
}
return false;
}
function getIfChildrenCount(ifNode: ts.IfStatement, allComponentNames: Set<string>): number {
const maxCount: number =
Math.max(getStatementCount(ifNode.thenStatement, allComponentNames),
getStatementCount(ifNode.elseStatement, allComponentNames));
return maxCount;
}
function getStatementCount(node: ts.Node, allComponentNames: Set<string>): number {
let maxCount: number = 0;
if (!node) {
return maxCount;
} else if (ts.isBlock(node)) {
maxCount = getBlockChildrenCount(node, allComponentNames);
} else if (ts.isIfStatement(node)) {
maxCount = getIfChildrenCount(node, allComponentNames);
} else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
isForEachComponent(node.expression)) {
maxCount = 2;
} else if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
!isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames)) {
maxCount = 1;
}
return maxCount;
}
function checkSpecificChildComponent(node: ts.Node, allComponentNames: Set<string>,
sourceFileNode: ts.SourceFile, log: LogInfo[]): void {
if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression) &&
ts.isIdentifier(node.expression.expression) && hasNonspecificChild(node, allComponentNames)) {
const componentName: string = node.expression.expression.escapedText.toString();
const pos: number = node.expression.expression.getStart();
const specificChildArray: string =
Array.from(SPECIFIC_CHILD_COMPONENT.get(componentName)).join(' and ');
const message: string =
`The component '${componentName}' can only have the child component ${specificChildArray}.`;
addLog(LogType.ERROR, message, pos, log, sourceFileNode);
}
}
function hasNonspecificChild(node: ts.ExpressionStatement,
allComponentNames: Set<string>): boolean {
const etsComponentExpression: ts.EtsComponentExpression = node.expression as ts.EtsComponentExpression;
const nodeName: ts.Identifier = etsComponentExpression.expression as ts.Identifier;
const nodeNameString: string = nodeName.escapedText.toString();
const blockNode: ts.Block = getNextNode(etsComponentExpression);
let isNonspecific: boolean = false;
if (SPECIFIC_CHILD_COMPONENT.has(nodeNameString) && blockNode) {
const specificChildSet: Set<string> = SPECIFIC_CHILD_COMPONENT.get(nodeNameString);
isNonspecific = isNonspecificChildBlock(blockNode, specificChildSet, allComponentNames);
if (isNonspecific) {
return isNonspecific;
}
}
return isNonspecific;
}
function isNonspecificChildBlock(blockNode: ts.Block, specificChildSet: Set<string>,
allComponentNames: Set<string>): boolean {
if (blockNode.statements) {
const length: number = blockNode.statements.length;
for (let i = 0; i < length; ++i) {
const item: ts.Node = blockNode.statements[i];
if (ts.isIfStatement(item) && isNonspecificChildIf(item, specificChildSet, allComponentNames)) {
return true;
}
if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression) &&
isForEachComponent(item.expression) &&
isNonspecificChildForEach(item.expression, specificChildSet, allComponentNames)) {
return true;
}
if (ts.isBlock(item) && isNonspecificChildBlock(item, specificChildSet, allComponentNames)) {
return true;
}
if (ts.isExpressionStatement(item) && ts.isEtsComponentExpression(item.expression)) {
let newNode: any = item.expression;
while (newNode.expression) {
if (ts.isEtsComponentExpression(newNode) && ts.isIdentifier(newNode.expression) &&
!isForEachComponent(newNode) && isComponent(newNode, allComponentNames)) {
const isNonspecific: boolean =
isNonspecificChildNonForEach(item.expression, specificChildSet);
if (isNonspecific) {
return isNonspecific;
}
if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) {
++i;
}
}
newNode = newNode.expression;
}
}
}
}
return false;
}
function isNonspecificChildIf(node: ts.IfStatement, specificChildSet: Set<string>,
allComponentNames: Set<string>): boolean {
return isNonspecificChildIfStatement(node.thenStatement, specificChildSet, allComponentNames) ||
isNonspecificChildIfStatement(node.elseStatement, specificChildSet, allComponentNames);
}
function isNonspecificChildForEach(node: ts.EtsComponentExpression, specificChildSet: Set<string>,
allComponentNames: Set<string>): boolean {
if (ts.isEtsComponentExpression(node) && node.arguments &&
node.arguments.length > 1 && ts.isArrowFunction(node.arguments[1])) {
const arrowFunction: ts.ArrowFunction = node.arguments[1] as ts.ArrowFunction;
const body: ts.Block | ts.EtsComponentExpression | ts.IfStatement =
arrowFunction.body as ts.Block | ts.EtsComponentExpression | ts.IfStatement;
if (!body) {
return false;
}
if (ts.isBlock(body) && isNonspecificChildBlock(body, specificChildSet, allComponentNames)) {
return true;
}
if (ts.isIfStatement(body) && isNonspecificChildIf(body, specificChildSet, allComponentNames)) {
return true;
}
if (ts.isCallExpression(body) && isForEachComponent(body) &&
isNonspecificChildForEach(body, specificChildSet, allComponentNames)) {
return true;
}
if (ts.isEtsComponentExpression(body) && !isForEachComponent(body) &&
isComponent(body, allComponentNames) &&
isNonspecificChildNonForEach(body, specificChildSet)) {
return true;
}
}
return false;
}
function isNonspecificChildNonForEach(node: ts.EtsComponentExpression,
specificChildSet: Set<string>): boolean {
if (ts.isIdentifier(node.expression) &&
!specificChildSet.has(node.expression.escapedText.toString())) {
return true;
}
return false;
}
function isNonspecificChildIfStatement(node: ts.Node, specificChildSet: Set<string>,
allComponentNames: Set<string>): boolean {
if (!node) {
return false;
}
if (ts.isBlock(node) && isNonspecificChildBlock(node, specificChildSet, allComponentNames)) {
return true;
}
if (ts.isIfStatement(node) && isNonspecificChildIf(node, specificChildSet, allComponentNames)) {
return true;
}
if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
isForEachComponent(node.expression) &&
isNonspecificChildForEach(node.expression, specificChildSet, allComponentNames)) {
return true;
}
if (ts.isExpressionStatement(node) && ts.isEtsComponentExpression(node.expression) &&
!isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames) &&
isNonspecificChildNonForEach(node.expression, specificChildSet)) {
return true;
}
return false;
}
function collectComponentProps(node: ts.StructDeclaration): void {
const componentName: string = node.name.getText();
const ComponentSet: IComponentSet = getComponentSet(node);
propertyCollection.set(componentName, ComponentSet.properties);
stateCollection.set(componentName, ComponentSet.states);
linkCollection.set(componentName, ComponentSet.links);
propCollection.set(componentName, ComponentSet.props);
regularCollection.set(componentName, ComponentSet.regulars);
storagePropCollection.set(componentName, ComponentSet.storageProps);
storageLinkCollection.set(componentName, ComponentSet.storageLinks);
provideCollection.set(componentName, ComponentSet.provides);
consumeCollection.set(componentName, ComponentSet.consumes);
objectLinkCollection.set(componentName, ComponentSet.objectLinks);
localStorageLinkCollection.set(componentName, ComponentSet.localStorageLink);
localStoragePropCollection.set(componentName, ComponentSet.localStorageProp);
}
export function getComponentSet(node: ts.StructDeclaration): IComponentSet {
const properties: Set<string> = new Set();
const states: Set<string> = new Set();
const links: Set<string> = new Set();
const props: Set<string> = new Set();
const regulars: Set<string> = new Set();
const storageProps: Set<string> = new Set();
const storageLinks: Set<string> = new Set();
const provides: Set<string> = new Set();
const consumes: Set<string> = new Set();
const objectLinks: Set<string> = new Set();
const localStorageLink: Map<string, Set<string>> = new Map();
const localStorageProp: Map<string, Set<string>> = new Map();
traversalComponentProps(node, properties, regulars, states, links, props, storageProps,
storageLinks, provides, consumes, objectLinks, localStorageLink, localStorageProp);
return {
properties, regulars, states, links, props, storageProps, storageLinks, provides,
consumes, objectLinks, localStorageLink, localStorageProp
};
}
function traversalComponentProps(node: ts.StructDeclaration, properties: Set<string>,
regulars: Set<string>, states: Set<string>, links: Set<string>, props: Set<string>,
storageProps: Set<string>, storageLinks: Set<string>, provides: Set<string>,
consumes: Set<string>, objectLinks: Set<string>,
localStorageLink: Map<string, Set<string>>, localStorageProp: Map<string, Set<string>>): void {
let isStatic: boolean = true;
if (node.members) {
const currentMethodCollection: Set<string> = new Set();
node.members.forEach(item => {
if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) {
const propertyName: string = item.name.getText();
properties.add(propertyName);
if (!item.decorators || !item.decorators.length) {
regulars.add(propertyName);
} else {
isStatic = false;
for (let i = 0; i < item.decorators.length; i++) {
const decoratorName: string = item.decorators[i].getText().replace(/\(.*\)$/, '').trim();
if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) {
dollarCollection.add('$' + propertyName);
collectionStates(item.decorators[i], decoratorName, propertyName, states, links, props, storageProps,
storageLinks, provides, consumes, objectLinks, localStorageLink, localStorageProp);
}
}
}
}
if (ts.isMethodDeclaration(item) && item.name && ts.isIdentifier(item.name)) {
currentMethodCollection.add(item.name.getText());
}
});
classMethodCollection.set(node.name.getText(), currentMethodCollection);
}
isStaticViewCollection.set(node.name.getText(), isStatic);
}
function collectionStates(node: ts.Decorator, decorator: string, name: string,
states: Set<string>, links: Set<string>, props: Set<string>, storageProps: Set<string>,
storageLinks: Set<string>, provides: Set<string>, consumes: Set<string>, objectLinks: Set<string>,
localStorageLink: Map<string, Set<string>>, localStorageProp: Map<string, Set<string>>): void {
switch (decorator) {
case COMPONENT_STATE_DECORATOR:
states.add(name);
break;
case COMPONENT_LINK_DECORATOR:
links.add(name);
break;
case COMPONENT_PROP_DECORATOR:
props.add(name);
break;
case COMPONENT_STORAGE_PROP_DECORATOR:
storageProps.add(name);
break;
case COMPONENT_STORAGE_LINK_DECORATOR:
storageLinks.add(name);
break;
case COMPONENT_PROVIDE_DECORATOR:
provides.add(name);
break;
case COMPONENT_CONSUME_DECORATOR:
consumes.add(name);
break;
case COMPONENT_OBJECT_LINK_DECORATOR:
objectLinks.add(name);
break;
case COMPONENT_LOCAL_STORAGE_LINK_DECORATOR :
collectionlocalStorageParam(node, name, localStorageLink);
break;
case COMPONENT_LOCAL_STORAGE_PROP_DECORATOR:
collectionlocalStorageParam(node, name, localStorageProp);
break;
}
}
function collectionlocalStorageParam(node: ts.Decorator, name: string,
localStorage: Map<string, Set<string>>): void {
const localStorageParam: Set<string> = new Set();
if (node && ts.isCallExpression(node.expression) && node.expression.arguments &&
node.expression.arguments.length && ts.isStringLiteral(node.expression.arguments[0])) {
localStorage.set(name, localStorageParam.add(
node.expression.arguments[0].getText().replace(/\"|'/g, '')));
}
}
export interface ReplaceResult {
content: string,
log: LogInfo[]
}
export function sourceReplace(source: string, sourcePath: string): ReplaceResult {
let content: string = source;
const log: LogInfo[] = [];
content = preprocessExtend(content);
content = preprocessNewExtend(content);
// process @system.
content = processSystemApi(content, false, sourcePath);
return {
content: content,
log: log
};
}
export function preprocessExtend(content: string, extendCollection?: Set<string>): string {
const REG_EXTEND: RegExp = /@Extend(\s+)([^\.\s]+)\.([^\(]+)\(/gm;
return content.replace(REG_EXTEND, (item, item1, item2, item3) => {
collectExtend(EXTEND_ATTRIBUTE, item2, '__' + item2 + '__' + item3);
collectExtend(EXTEND_ATTRIBUTE, item2, item3);
if (extendCollection) {
extendCollection.add(item3);
}
return `@Extend(${item2})${item1}function __${item2}__${item3}(`;
});
}
export function preprocessNewExtend(content: string, extendCollection?: Set<string>): string {
const REG_EXTEND: RegExp = /@Extend\s*\([^\)]+\)\s*function\s+([^\(\s]+)\s*\(/gm;
return content.replace(REG_EXTEND, (item, item1) => {
if (extendCollection) {
extendCollection.add(item1);
}
return item;
});
}
export function processSystemApi(content: string, isProcessAllowList: boolean = false,
sourcePath: string = null, isSystemModule: boolean = false): string {
let REG_SYSTEM: RegExp;
if (isProcessAllowList) {
REG_SYSTEM =
/(import|const)\s+(.+)\s*=\s*(\_\_importDefault\()?require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)(\))?/g;
} else {
REG_SYSTEM =
/import\s+(.+)\s+from\s+['"]@(system|ohos)\.(\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)/g;
}
const REG_LIB_SO: RegExp =
/import\s+(.+)\s+from\s+['"]lib(\S+)\.so['"]|import\s+(.+)\s*=\s*require\(\s*['"]lib(\S+)\.so['"]\s*\)/g;
const systemValueCollection: Set<string> = new Set();
const newContent: string = content.replace(REG_LIB_SO, (_, item1, item2, item3, item4) => {
const libSoValue: string = item1 || item3;
const libSoKey: string = item2 || item4;
if (sourcePath) {
useOSFiles.add(sourcePath);
}
return `var ${libSoValue} = globalThis.requireNapi("${libSoKey}", true);`;
}).replace(REG_SYSTEM, (item, item1, item2, item3, item4, item5, item6, item7) => {
let moduleType: string = item2 || item5;
let systemKey: string = item3 || item6;
let systemValue: string = item1 || item4;
if (!VALIDATE_MODULE.includes(systemValue)) {
importModuleCollection.add(systemValue);
}
if (!isProcessAllowList && !isSystemModule) {
return item;
} else if (isProcessAllowList) {
systemValue = item2;
moduleType = item4;
systemKey = item5;
systemValueCollection.add(systemValue);
}
moduleCollection.add(`${moduleType}.${systemKey}`);
if (NATIVE_MODULE.has(`${moduleType}.${systemKey}`)) {
item = `var ${systemValue} = globalThis.requireNativeModule('${moduleType}.${systemKey}')`;
} else if (moduleType === SYSTEM_PLUGIN) {
item = `var ${systemValue} = isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ? ` +
`globalThis.systemplugin.${systemKey} : globalThis.requireNapi('${systemKey}')`;
} else if (moduleType === OHOS_PLUGIN) {
item = `var ${systemValue} = globalThis.requireNapi('${systemKey}') || ` +
`(isSystemplugin('${systemKey}', '${OHOS_PLUGIN}') ? ` +
`globalThis.ohosplugin.${systemKey} : isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ` +
`? globalThis.systemplugin.${systemKey} : undefined)`;
}
return item;
});
return processInnerModule(newContent, systemValueCollection);
}
function processInnerModule(content: string, systemValueCollection: Set<string>): string {
systemValueCollection.forEach(element => {
const target: string = element.trim() + '.default';
while (content.includes(target)) {
content = content.replace(target, element.trim());
}
});
return content;
}
const VALIDATE_MODULE_REG: RegExp = new RegExp('^(' + VALIDATE_MODULE.join('|') + ')');
function validateAllowListModule(moduleType: string, systemKey: string): boolean {
return moduleType === 'ohos' && VALIDATE_MODULE_REG.test(systemKey);
}
export function resetComponentCollection() {
componentCollection.entryComponent = null;
componentCollection.entryComponentPos = null;
componentCollection.previewComponent = null;
}