diff --git a/compiler/build_declarations_file.js b/compiler/build_declarations_file.js index 3e2e53d..41d982b 100644 --- a/compiler/build_declarations_file.js +++ b/compiler/build_declarations_file.js @@ -17,10 +17,24 @@ const ts = require('typescript') const path = require('path') const fs = require('fs') +const addTSInterfaceSet = ['ForEach', 'LazyForEach', 'TapGesture', 'LongPressGesture', 'LongPressGesture', + 'PanGesture', 'SwipeGesture', 'PinchGesture', 'RotationGesture', 'GestureGroup','PageTransitionEnter', + 'PageTransitionExit']; +const addTSAttributeSet = ['AlphabetIndexer', 'Animator', 'Badge', 'Blank', 'Button', 'Calendar', 'Canvas', + 'Checkbox', 'CheckboxGroup', 'Circle', 'Column', 'ColumnSplit', 'Counter', 'DataPanel', 'DatePicker', + 'Divider', 'Ellipse', 'Flex', 'FormComponent', 'Gauge', 'Grid', 'GridItem', 'GridContainer', 'Image', + 'ImageAnimator', 'Line', 'List', 'ListItem', 'LoadingProgress', 'Marquee', 'Navigation', 'Navigator', + 'Panel', 'Path', 'PatternLock', 'Piece', 'PluginComponent', 'Polygon', 'Polyline', 'Progress', + 'QRCode', 'Radio', 'Rating', 'Rect', 'Refresh', 'Row', 'RowSplit', 'Scroll', 'ScrollBar', 'Search', + 'Select', 'Shape', 'Sheet', 'Slider', 'Span', 'Stack', 'Stepper', 'StepperItem', 'Swiper', + 'TabContent', 'Tabs', 'Text', 'TextArea', 'TextClock', 'TextInput', 'TextPicker', 'TextTimer', + 'Toggle', 'Video', 'Web', 'XComponent', 'RichText']; + generateTargetFile(process.argv[2], process.argv[3]); function generateTargetFile(filePath, output) { const files = []; const globalTsFile = path.resolve(filePath, '../../global.d.ts'); + const middleTsFile = path.resolve(filePath, 'middle_class.d.ts'); if (fs.existsSync(globalTsFile)) { files.push(globalTsFile); } @@ -47,7 +61,7 @@ function generateTargetFile(filePath, output) { const fileName = path.resolve(output, path.basename(item)); if (item === globalTsFile) { content = license + '\n\n' + processsFile(content, fileName, true); - } else { + } else if (path.resolve(item) !== middleTsFile) { content = license + '\n\n' + processsFile(content, fileName, false); } fs.writeFile(fileName, content, err => { @@ -127,6 +141,18 @@ function processComponent(node, newStatements) { node = ts.factory.updateInterfaceDeclaration(node, node.decorators, node.modifiers, node.name, node.typeParameters, [heritageClause], node.members); } + if (addTSInterfaceSet.includes(componentName)) { + node = ts.factory.updateInterfaceDeclaration(node, node.decorators, node.modifiers, node.name, + node.typeParameters, [ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, + [ts.factory.createExpressionWithTypeArguments(ts.factory.createIdentifier('TS' + componentName + 'Interface'), + undefined)])], node.members); + } + } + if (isClass(node) && addTSAttributeSet.includes(node.name.getText().replace(/Attribute$/, ''))) { + node = ts.factory.updateClassDeclaration(node, node.decorators, node.modifiers, node.name, + node.typeParameters, [ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, + [ts.factory.createExpressionWithTypeArguments(ts.factory.createIdentifier('TS' + node.name.getText()), + undefined)])], node.members); } newStatements.push(node); } @@ -164,6 +190,11 @@ function isInterface(node) { /Interface$/.test(node.name.getText()); } +function isClass(node) { + return ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name) && + /Attribute$/.test(node.name.getText()); +} + function isSignNode(node) { return (ts.isCallSignatureDeclaration(node) || ts.isConstructSignatureDeclaration(node)) && node.type && ts.isTypeReferenceNode(node.type) && node.type.typeName && ts.isIdentifier(node.type.typeName) && diff --git a/compiler/components/button.json b/compiler/components/button.json index 385a0d9..545d255 100644 --- a/compiler/components/button.json +++ b/compiler/components/button.json @@ -1,4 +1,5 @@ { "name": "Button", + "single": true, "attrs": ["type", "stateEffect", "fontColor", "fontSize", "fontWeight", "fontStyle", "fontFamily"] } \ No newline at end of file diff --git a/compiler/components/grid.json b/compiler/components/grid.json index 1a9682a..260ee96 100644 --- a/compiler/components/grid.json +++ b/compiler/components/grid.json @@ -5,6 +5,6 @@ "columnsTemplate", "rowsTemplate", "columnsGap", "rowsGap", "scrollBar", "scrollBarWidth", "scrollBarColor", "editMode", "maxCount", "minCount", "cellLength", "onItemDragStart", "onItemDragMove", "onItemDragEnter", "onItemDragMove", "onItemDrop", "layoutDirection", "direction", - "supportAnimation", "onItemDragLeave" + "supportAnimation", "onItemDragLeave", "multiSelectable" ] } \ No newline at end of file diff --git a/compiler/components/gridItem.json b/compiler/components/gridItem.json index be369c1..e245093 100644 --- a/compiler/components/gridItem.json +++ b/compiler/components/gridItem.json @@ -2,5 +2,5 @@ "name": "GridItem", "parents": ["Grid"], "single": true, - "attrs": ["rowStart", "rowEnd", "columnStart", "columnEnd", "forceRebuild"] + "attrs": ["rowStart", "rowEnd", "columnStart", "columnEnd", "forceRebuild", "selectable", "onSelect"] } \ No newline at end of file diff --git a/compiler/components/list.json b/compiler/components/list.json index acf726d..3655d8f 100644 --- a/compiler/components/list.json +++ b/compiler/components/list.json @@ -4,6 +4,7 @@ "attrs": [ "listDirection", "scrollBar", "edgeEffect", "divider", "editMode", "cachedCount", "chainAnimation", "onScroll", "onReachStart", "onReachEnd", "onScrollStop", "onItemDelete", - "onItemMove", "onItemDragStart", "onItemDragEnter", "onItemDragMove", "onItemDragLeave", "onItemDrop" + "onItemMove", "onItemDragStart", "onItemDragEnter", "onItemDragMove", "onItemDragLeave", "onItemDrop", + "multiSelectable" ] } \ No newline at end of file diff --git a/compiler/components/listItem.json b/compiler/components/listItem.json index 467c66e..7c704e6 100644 --- a/compiler/components/listItem.json +++ b/compiler/components/listItem.json @@ -2,5 +2,5 @@ "name": "ListItem", "parents": ["List"], "single": true, - "attrs": ["sticky", "editable"] + "attrs": ["sticky", "editable", "selectable", "onSelect"] } \ No newline at end of file diff --git a/compiler/components/rich_text.json b/compiler/components/rich_text.json new file mode 100644 index 0000000..cf67685 --- /dev/null +++ b/compiler/components/rich_text.json @@ -0,0 +1,7 @@ +{ + "name": "RichText", + "single": true, + "attrs": [ + "onStart", "onComplete" + ] +} \ No newline at end of file diff --git a/compiler/components/sideBar_container.json b/compiler/components/sideBar_container.json index ec9f989..28e4e0f 100644 --- a/compiler/components/sideBar_container.json +++ b/compiler/components/sideBar_container.json @@ -1,6 +1,6 @@ { "name": "SideBarContainer", "attrs": [ - "showControlButton", "onChange" + "showControlButton", "onChange", "sideBarWidth", "maxSideBarWidth", "maxSideBarWidth" ] } \ No newline at end of file diff --git a/compiler/components/slider.json b/compiler/components/slider.json index 6198264..caf66ef 100644 --- a/compiler/components/slider.json +++ b/compiler/components/slider.json @@ -2,7 +2,7 @@ "name": "Slider", "atomic": true, "attrs": [ - "blockColor", "trackColor", "selectedColor", "minLabel", "maxLabel", "showSteps", "showTips", + "blockColor", "trackColor", "selectedColor", "minLabel", "maxLabel", "showSteps", "showTips", "trackThickness", "onChange" ] -} \ No newline at end of file +} diff --git a/compiler/components/swiper.json b/compiler/components/swiper.json index e618983..e54b1b9 100644 --- a/compiler/components/swiper.json +++ b/compiler/components/swiper.json @@ -2,6 +2,7 @@ "name": "Swiper", "attrs": [ "index", "autoPlay", "interval", "indicator", "loop", "duration", "vertical", "itemSpace", - "cachedCount", "displayMode", "displayCount", "effectMode", "disableSwipe", "onChange", "indicatorStyle" + "cachedCount", "displayMode", "displayCount", "effectMode", "disableSwipe", "curve", "onChange", + "indicatorStyle" ] } \ No newline at end of file diff --git a/compiler/components/tab_content.json b/compiler/components/tab_content.json index e0beff0..5592200 100644 --- a/compiler/components/tab_content.json +++ b/compiler/components/tab_content.json @@ -1,5 +1,6 @@ { "name": "TabContent", "parents": ["Tabs"], - "attrs": ["tabBar"] + "attrs": ["tabBar"], + "single": true } \ No newline at end of file diff --git a/compiler/components/text_clock.json b/compiler/components/text_clock.json index 3b23350..c139319 100644 --- a/compiler/components/text_clock.json +++ b/compiler/components/text_clock.json @@ -1,7 +1,7 @@ { "name": "TextClock", "attrs": [ - "format", "onDateChange", "status", "fontColor", + "format", "onDateChange", "fontColor", "fontSize", "fontStyle", "fontWeight", "fontFamily" ] -} \ No newline at end of file +} diff --git a/compiler/components/web.json b/compiler/components/web.json index a2c7a48..4cb5105 100644 --- a/compiler/components/web.json +++ b/compiler/components/web.json @@ -1,5 +1,12 @@ { "name": "Web", "atomic": true, - "attrs": ["onPageFinish", "onRequestFocus", "javaScriptEnabled", "fileAccessEnabled"] + "attrs": [ + "password", "cacheMode", "tableData", "wideViewModeAccess", "overviewModeAccess", "textZoomAtio", "databaseAccess", + "onRefreshAccessedHistory", "onUrlLoadIntercept", "onSslErrorReceive", "onRenderExited", "onFileSelectorShow", + "onPageEnd", "onPageBegin", "onProgressChange", "onTitleReceive", "onGeolocationHide", "onGeolocationShow", + "onRequestSelected", "javaScriptAccess", "fileAccess", "onAlert", "onBeforeUnload", "onlineImageAccess", + "domStorageAccess", "imageAccess", "mixedMode", "zoomAccess", "geolocationAccess", "javaScriptProxy", + "onConfirm", "onConsole", "onErrorReceive", "onHttpErrorReceive", "onDownloadStart" + ] } \ No newline at end of file diff --git a/compiler/main.js b/compiler/main.js index be5e7fd..aa25d1a 100644 --- a/compiler/main.js +++ b/compiler/main.js @@ -73,6 +73,7 @@ function loadEntryObj(projectConfig) { const jsonString = fs.readFileSync(projectConfig.manifestFilePath).toString(); manifest = JSON.parse(jsonString); } else if (projectConfig.aceModuleJsonPath && fs.existsSync(projectConfig.aceModuleJsonPath)) { + process.env.compileMode = 'moduleJson'; buildManifest(manifest, projectConfig.aceModuleJsonPath); } else { throw Error('\u001b[31m ERROR: the manifest file ' + projectConfig.manifestFilePath + @@ -115,7 +116,7 @@ function buildManifest(manifest, aceConfigPath) { function getPages(configJson) { const pages = [] const modulePagePath = path.resolve(projectConfig.aceProfilePath, - `${configJson.module.pages.replace(/\@profile\:/, '')}.json`); + `${configJson.module.pages.replace(/\$profile\:/, '')}.json`); if (fs.existsSync(modulePagePath)) { const pagesConfig = JSON.parse(fs.readFileSync(modulePagePath, 'utf-8')); if (pagesConfig && pagesConfig.src) { diff --git a/compiler/server/build_pipe_server.js b/compiler/server/build_pipe_server.js index a087c32..4d3e8a6 100644 --- a/compiler/server/build_pipe_server.js +++ b/compiler/server/build_pipe_server.js @@ -57,7 +57,7 @@ function handlePluginCompileComponent(jsonData) { const newSource = ts.factory.updateSourceFile(sourceNode, previewStatements); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); const result = printer.printNode(ts.EmitHint.Unspecified, newSource, newSource); - receivedMsg.data.script = result; + receivedMsg.data.script = ts.transpileModule(result, {}).outputText; if (pluginSocket.readyState === WebSocket.OPEN){ responseToPlugin(receivedMsg); } diff --git a/compiler/src/compile_info.ts b/compiler/src/compile_info.ts index bf509f0..c279652 100644 --- a/compiler/src/compile_info.ts +++ b/compiler/src/compile_info.ts @@ -92,7 +92,7 @@ export class ResultStates { } ); - compilation.hooks.buildModule.tap("findModule", (module) => { + compilation.hooks.buildModule.tap('findModule', (module) => { if (/node_modules/.test(module.context)) { const modulePath: string = path.resolve(module.resourceResolveData.descriptionFileRoot, MODULE_SHARE_PATH); @@ -113,7 +113,7 @@ export class ResultStates { compilation.hooks.processAssets.tap( { name: 'GLOBAL_COMMON_MODULE_CACHE', - stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, + stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS }, (assets) => { if (assets['commons.js']) { @@ -133,7 +133,7 @@ export class ResultStates { `globalThis["__common_module_cache__"][moduleId]: null;\n` + `if (commonCachedModule) { return commonCachedModule.exports; }\n` + source.replace('// Execute the module function', - `if (globalThis["__common_module_cache__"] && moduleId.indexOf("?name=") < 0 && ` + + `if (globalThis["__common_module_cache__"] && moduleId.indexOf("?name=") < 0 && ` + `Object.keys(globalThis["__common_module_cache__"]).indexOf(moduleId) >= 0) {\n` + ` globalThis["__common_module_cache__"][moduleId] = module;\n}`); }); @@ -157,12 +157,12 @@ export class ResultStates { compiler.hooks.compilation.tap('Collect Components And Modules', compilation => { compilation.hooks.additionalAssets.tapAsync('Collect Components And Modules', callback => { compilation.assets['./component_collection.txt'] = - new RawSource(Array.from(appComponentCollection).join(",")); + new RawSource(Array.from(appComponentCollection).join(',')); compilation.assets['./module_collection.txt'] = - new RawSource(moduleCollection.size === 0 ? 'NULL' : Array.from(moduleCollection).join(",")); + new RawSource(moduleCollection.size === 0 ? 'NULL' : Array.from(moduleCollection).join(',')); callback(); }); - }) + }); } } @@ -257,7 +257,7 @@ export class ResultStates { const componentNameReg: RegExp = /'typeof\s*(\$?[_a-zA-Z0-9]+)' is not callable/; const stateInfoReg: RegExp = /Property\s*'(\$[_a-zA-Z0-9]+)' does not exist on type/; const extendInfoReg: RegExp = - /Property\s*'([_a-zA-Z0-9]+)' does not exist on type\s*'([_a-zA-Z0-9]+)(Attribute|Interface)?'\./; + /Property\s*'([_a-zA-Z0-9]+)' does not exist on type\s*'([_a-zA-Z0-9]+)(Attribute|Interface)'\./; if (this.matchMessage(message, props.concat([...STYLES_ATTRIBUTE]), propInfoReg) || this.matchMessage(message, [...componentCollection.customComponents], componentNameReg) || this.matchMessage(message, props, stateInfoReg) || diff --git a/compiler/src/component_map.ts b/compiler/src/component_map.ts index 5d8bd85..19083b5 100644 --- a/compiler/src/component_map.ts +++ b/compiler/src/component_map.ts @@ -78,17 +78,22 @@ export interface ExtendParamterInterfance { export const EXTEND_ATTRIBUTE: Map> = new Map(); export const STYLES_ATTRIBUTE: Set = new Set(); +export const INTERFACE_NODE_SET: Set = new Set(); + export const JS_BIND_COMPONENTS: Set = new Set([ ...GESTURE_TYPE_NAMES, 'Gesture', 'PanGestureOption', 'CustomDialogController', 'Storage', 'Scroller', 'SwiperController', 'TabsController', 'CalendarController', 'AbilityController', 'VideoController', 'WebController', 'XComponentController', 'CanvasRenderingContext2D', 'CanvasGradient', 'ImageBitmap', 'ImageData', 'Path2D', 'RenderingContextSettings', 'OffscreenCanvasRenderingContext2D', 'DatePickerDialog', - 'TextPickerDialog', 'AlertDialog', 'ActionSheet', 'PatternLockController' + 'TextPickerDialog', 'AlertDialog', 'ContextMenu', 'ActionSheet', 'PatternLockController' ]); export const NEEDPOP_COMPONENT: Set = new Set(['Blank', 'Search']); +export const CUSTOM_BUILDER_PROPERTIES: Set = new Set(['bindPopup', 'bindMenu', 'bindContextMenu', 'title', + 'menus', 'toolBar', 'tabBar']); + (function initComponent() { Object.keys(COMPONENT_MAP).forEach((componentName) => { INNER_COMPONENT_NAMES.add(componentName); diff --git a/compiler/src/pre_define.ts b/compiler/src/pre_define.ts index 33f4f21..f166b9c 100644 --- a/compiler/src/pre_define.ts +++ b/compiler/src/pre_define.ts @@ -17,6 +17,7 @@ import path from 'path'; export const NATIVE_MODULE: Set = new Set( ['system.app', 'ohos.app', 'system.router', 'system.curves', 'ohos.curves', 'system.matrix4', 'ohos.matrix4']); +export const VALIDATE_MODULE: string[] = ['application', 'util', 'screen']; export const SYSTEM_PLUGIN: string = 'system'; export const OHOS_PLUGIN: string = 'ohos'; @@ -182,14 +183,18 @@ export const GEOMETRY_VIEW: string = 'GeometryView'; export const MODULE_SHARE_PATH: string = 'src' + path.sep + 'ets' + path.sep + 'share'; export const BUILD_SHARE_PATH: string = '../share'; -export const CHILD: string = 'child'; export const THIS: string = 'this'; export const STYLES: string = 'Styles'; export const VISUAL_STATE: string = 'visualState'; export const VIEW_STACK_PROCESSOR: string = 'ViewStackProcessor'; export const BIND_POPUP: string = 'bindPopup'; +export const CHECKED: string = 'checked'; +export const RADIO: string = 'Radio'; export const $$_VALUE: string = 'value'; export const $$_CHANGE_EVENT: string = 'changeEvent'; export const $$_THIS: string = '$$this'; export const $$_NEW_VALUE: string = 'newValue'; + +export const INTERFACE_NAME_SUFFIX:string = '_Params'; +export const OBSERVED_PROPERTY_ABSTRACT:string = 'ObservedPropertyAbstract'; diff --git a/compiler/src/pre_process.ts b/compiler/src/pre_process.ts index 8e02c3f..bab5b8c 100644 --- a/compiler/src/pre_process.ts +++ b/compiler/src/pre_process.ts @@ -52,25 +52,25 @@ function preProcess(source: string): string { function parseVisual(resourcePath: string, content: string, log: LogInfo[]): string { if (componentCollection.entryComponent && projectConfig.aceSuperVisualPath) { const sourceFile: ts.SourceFile = ts.createSourceFile(resourcePath, content, - ts.ScriptTarget.Latest, true, ts.ScrpitKint.TS); - if (sourceFile.statments) { - sourceFile.statments.forEach(statment => { - content = parseStatment(statment, content, log, resourcePath); + ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + if (sourceFile.statements) { + sourceFile.statements.forEach(statement => { + content = parseStatement(statement, content, log, resourcePath); }); } } return content; } -function parseStatment(statement: ts.Statement, content: string, log: LogInfo[], +function parseStatement(statement: ts.Statement, content: string, log: LogInfo[], resourcePath: string): string { if (statement.kind === ts.SyntaxKind.ClassDeclaration && statement.name && statement.name.getText() === componentCollection.entryComponent) { const visualPath: string = findVisualFile(resourcePath); if (visualPath && fs.existsSync(visualPath) && statement.members) { statement.members.forEach(member => { - if (member.kind && member === ts.SyntaxKind.MethodDeclaration) { - content = parseMember(member, content, log, visualPath) + if (member.kind && member.kind === ts.SyntaxKind.MethodDeclaration) { + content = parseMember(member, content, log, visualPath); } }); } @@ -108,7 +108,7 @@ function findVisualFile(filePath: string): string { function getVisualContent(visualPath: string, log: LogInfo[], pos: number): string { const parseContent: any = genETS(fs.readFileSync(visualPath, 'utf-8')); - if (parseContent && parseContent.errorType && parseContent.errorType != '') { + if (parseContent && parseContent.errorType && parseContent.errorType !== '') { log.push({ type: LogType.ERROR, message: parseContent.message, @@ -118,4 +118,4 @@ function getVisualContent(visualPath: string, log: LogInfo[], pos: number): stri return parseContent ? parseContent.ets : null; } -module.exports = preProcess; \ No newline at end of file +module.exports = preProcess; diff --git a/compiler/src/process_component_build.ts b/compiler/src/process_component_build.ts index 29c04bc..b5ecb19 100644 --- a/compiler/src/process_component_build.ts +++ b/compiler/src/process_component_build.ts @@ -45,7 +45,6 @@ import { COMPONENT_TRANSITION_NAME, COMPONENT_DEBUGLINE_FUNCTION, ATTRIBUTE_STATESTYLES, - CHILD, THIS, VISUAL_STATE, VIEW_STACK_PROCESSOR, @@ -53,7 +52,12 @@ import { $$_VALUE, $$_CHANGE_EVENT, $$_THIS, - $$_NEW_VALUE + $$_NEW_VALUE, + CHECKED, + RADIO, + BUILDER_ATTR_NAME, + BUILDER_ATTR_BIND, + CUSTOM_DIALOG_CONTROLLER_BUILDER } from './pre_define'; import { INNER_COMPONENT_NAMES, @@ -67,7 +71,8 @@ import { NEEDPOP_COMPONENT, INNER_STYLE_FUNCTION, GLOBAL_STYLE_FUNCTION, - COMMON_ATTRS + COMMON_ATTRS, + CUSTOM_BUILDER_PROPERTIES } from './component_map'; import { componentCollection } from './validate_ui_syntax'; import { processCustomComponent } from './process_custom_component'; @@ -169,15 +174,17 @@ export function processComponentChild(node: ts.Block | ts.SourceFile, newStateme break; case ComponentType.customComponent: if (index + 1 < array.length && ts.isBlock(array[index + 1])) { - item = processBlockChange(item, - array[index + 1] as ts.Block, log) + if (processExpressionStatementChange(item, array[index + 1] as ts.Block, log)) { + item = processExpressionStatementChange(item, array[index + 1] as ts.Block, log); + } } - processCustomComponent(item, newStatements, log); + processCustomComponent(item as ts.ExpressionStatement, newStatements, log); break; case ComponentType.forEachComponent: processForEachComponent(item, newStatements, log); break; - case ComponentType.customBuilderMethod || ComponentType.builderParamMethod: + case ComponentType.customBuilderMethod: + case ComponentType.builderParamMethod: newStatements.push(item); break; } @@ -195,28 +202,47 @@ export function processComponentChild(node: ts.Block | ts.SourceFile, newStateme } } -function processBlockChange(node: ts.ExpressionStatement, nextNode: ts.Block, - log: LogInfo[]): ts.block { +function processExpressionStatementChange(node: ts.ExpressionStatement, nextNode: ts.Block, + log: LogInfo[]): ts.ExpressionStatement { + let name: string; // @ts-ignore + if (node.expression.expression && ts.isIdentifier(node.expression.expression)) { + name = node.expression.expression.escapedText.toString(); + } + if (builderParamObjectCollection.get(name) && + builderParamObjectCollection.get(name).size === 1) { + return processBlockToExpression(node, nextNode, log, name); + } else { + log.push({ + type: LogType.ERROR, + message: `In the trailing lambda case, '${name}' must have one and only one property decorated with ` + + `@BuilderParam, and its @BuilderParam expects no parameter.`, + pos: node.getStart() + }); + } +} + +function processBlockToExpression(node: ts.ExpressionStatement, nextNode: ts.Block, + log: LogInfo[], name: string): ts.ExpressionStatement { + const childParam: string = [...builderParamObjectCollection.get(name)].slice(-1)[0]; const newBlock: ts.Block = processComponentBlock(nextNode, false, log); const arrowNode: ts.ArrowFunction = ts.factory.createArrowFunction(undefined, undefined, [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), newBlock); const newPropertyAssignment:ts.PropertyAssignment = ts.factory.createPropertyAssignment( - ts.factory.createIdentifier(CHILD), arrowNode); + ts.factory.createIdentifier(childParam), arrowNode); // @ts-ignore - let argumentsArray: ts.ObjectLiteralExpression[] = node.express.arguments; - if (arguments && arguments.length < 1) { - argumentsArray = [ts.factory.createObjectLiteralExpression([newPropertyAssignment], true)] + let argumentsArray: ts.ObjectLiteralExpression[] = node.expression.arguments; + if (argumentsArray && !argumentsArray.length) { + argumentsArray = [ts.factory.createObjectLiteralExpression([newPropertyAssignment], true)]; } else { - // @ts-ignore argumentsArray = [ts.factory.createObjectLiteralExpression( // @ts-ignore - node.express.arguments[0].properties.concat([newPropertyAssignment]), true)] + node.expression.arguments[0].properties.concat([newPropertyAssignment]), true)]; } - // @ts-ignore - node = ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression(node.expression, + node = ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression( // @ts-ignore - node.expression.expression, node.expression.expression,typeArguments, argumentsArray)) + node.expression, node.expression.expression, node.expression.expression.typeArguments, + argumentsArray)); return node; } @@ -491,21 +517,31 @@ interface AnimationInfo { export function bindComponentAttr(node: ts.ExpressionStatement, identifierNode: ts.Identifier, newStatements: ts.Statement[], log: LogInfo[], reverse: boolean = true, - isStylesAttr: boolean = false): void { + isStylesAttr: boolean = false, isGlobalStyles: boolean = false): void { let temp: any = node.expression; const statements: ts.Statement[] = []; const lastStatement: AnimationInfo = { statement: null, kind: false }; while (temp && ts.isCallExpression(temp) && temp.expression) { + if (temp.expression && (validatePropertyAccessExpressionWithCustomBuilder(temp.expression) || + validateIdentifierWithCustomBuilder(temp.expression))) { + let propertyName: string = ''; + if (ts.isIdentifier(temp.expression)) { + propertyName = temp.expression.escapedText.toString(); + } else if (ts.isPropertyAccessExpression(temp.expression)) { + propertyName = temp.expression.name.escapedText.toString(); + } + temp = propertyName === BIND_POPUP ? processBindPopupBuilder(temp) : processCustomBuilderProperty(temp); + } if (ts.isPropertyAccessExpression(temp.expression) && temp.expression.name && ts.isIdentifier(temp.expression.name)) { addComponentAttr(temp, temp.expression.name, lastStatement, statements, identifierNode, log, - isStylesAttr); + isStylesAttr, isGlobalStyles); temp = temp.expression.expression; } else if (ts.isIdentifier(temp.expression)) { if (!INNER_COMPONENT_NAMES.has(temp.expression.getText()) && !GESTURE_TYPE_NAMES.has(temp.expression.getText())) { addComponentAttr(temp, temp.expression, lastStatement, statements, identifierNode, log, - isStylesAttr); + isStylesAttr, isGlobalStyles); } break; } @@ -518,7 +554,131 @@ export function bindComponentAttr(node: ts.ExpressionStatement, identifierNode: } } -function createArrowFunctionFor$$ ($$varExp: ts.Expression): ts.ArrowFunction { +function processCustomBuilderProperty(node: ts.CallExpression): ts.CallExpression { + const newArguments: ts.Expression[] = []; + node.arguments.forEach((argument: ts.Expression | ts.Identifier, index: number) => { + if (index === 0 && isBuilderChangeNode(argument)) { + newArguments.push(parseBuilderNode(argument)); + } else { + newArguments.push(argument); + } + }); + node = ts.factory.updateCallExpression(node, node.expression, node.typeArguments, newArguments); + return node; +} + +function isBuilderChangeNode(argument: ts.Node): boolean { + return (ts.isPropertyAccessExpression(argument) && argument.name && ts.isIdentifier(argument.name) + && CUSTOM_BUILDER_METHOD.has(argument.name.getText())) || + (ts.isCallExpression(argument) && argument.expression && argument.expression.name && + ts.isIdentifier(argument.expression.name) && + CUSTOM_BUILDER_METHOD.has(argument.expression.name.getText())) || (ts.isIdentifier(argument) && + argument.escapedText && CUSTOM_BUILDER_METHOD.has(argument.escapedText.toString())); +} + +function parseBuilderNode(node: ts.Node): ts.ObjectLiteralExpression { + if (isPropertyAccessExpressionNode(node)) { + return processPropertyBuilder(node as ts.PropertyAccessExpression); + } else if (ts.isIdentifier(node) && CUSTOM_BUILDER_METHOD.has(node.escapedText.toString())) { + return processIdentifierBuilder(node); + } else if (ts.isCallExpression(node)) { + return getParsedBuilderAttrArgumentWithParams(node); + } +} + +function isPropertyAccessExpressionNode(node: ts.Node): boolean { + return ts.isPropertyAccessExpression(node) && node.expression && + node.expression.kind === ts.SyntaxKind.ThisKeyword && node.name && ts.isIdentifier(node.name) && + CUSTOM_BUILDER_METHOD.has(node.name.escapedText.toString()); +} + +function processBindPopupBuilder(node: ts.CallExpression): ts.CallExpression { + const newArguments: ts.Expression[] = []; + node.arguments.forEach((argument: ts.ObjectLiteralExpression, index: number) => { + if (index === 1) { + // @ts-ignore + newArguments.push(processBindPopupBuilderProperty(argument)); + } else { + newArguments.push(argument); + } + }); + node = ts.factory.updateCallExpression(node, node.expression, node.typeArguments, newArguments); + return node; +} + +function processBindPopupBuilderProperty(node: ts.ObjectLiteralExpression): ts.ObjectLiteralExpression { + const newProperties: ts.PropertyAssignment[] = []; + node.properties.forEach((property: ts.PropertyAssignment, index: number) => { + if (index === 0) { + if (property.name && ts.isIdentifier(property.name) && + property.name.escapedText.toString() === CUSTOM_DIALOG_CONTROLLER_BUILDER) { + newProperties.push(ts.factory.updatePropertyAssignment(property, property.name, + parseBuilderNode(property.initializer))); + } else { + newProperties.push(property); + } + } else { + newProperties.push(property); + } + }); + return ts.factory.updateObjectLiteralExpression(node, newProperties); +} + +function processPropertyBuilder(node: ts.PropertyAccessExpression): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(BUILDER_ATTR_NAME), + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + node, + ts.factory.createIdentifier(BUILDER_ATTR_BIND) + ), + undefined, + [ts.factory.createThis()] + ) + ) + ]); +} + +function processIdentifierBuilder(node: ts.Identifier): ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(BUILDER_ATTR_NAME), + node + ) + ]); +} + +function getParsedBuilderAttrArgumentWithParams(node: ts.CallExpression): + ts.ObjectLiteralExpression { + return ts.factory.createObjectLiteralExpression([ + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(BUILDER_ATTR_NAME), + ts.factory.createArrowFunction( + undefined, + undefined, + [], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock( + [ts.factory.createExpressionStatement(node)], + true + ) + ) + ) + ]); +} + +function validatePropertyAccessExpressionWithCustomBuilder(node: ts.Node): boolean { + return ts.isPropertyAccessExpression(node) && node.name && + ts.isIdentifier(node.name) && CUSTOM_BUILDER_PROPERTIES.has(node.name.escapedText.toString()); +} + +function validateIdentifierWithCustomBuilder(node: ts.Node): boolean { + return ts.isIdentifier(node) && CUSTOM_BUILDER_PROPERTIES.has(node.escapedText.toString()); +} + +function createArrowFunctionFor$$($$varExp: ts.Expression): ts.ArrowFunction { return ts.factory.createArrowFunction( undefined, undefined, [ts.factory.createParameterDeclaration( @@ -541,8 +701,8 @@ function createArrowFunctionFor$$ ($$varExp: ts.Expression): ts.ArrowFunction { function updateArgumentFor$$(argument: any): ts.Expression { if (ts.isElementAccessExpression(argument)) { - return ts.factory.updateElementAccessExpression - (argument, updateArgumentFor$$(argument.expression), argument.argumentExpression); + return ts.factory.updateElementAccessExpression( + argument, updateArgumentFor$$(argument.expression), argument.argumentExpression); } else if (ts.isIdentifier(argument)) { props.push(argument.getText()); if (argument.getText() === $$_THIS) { @@ -551,14 +711,14 @@ function updateArgumentFor$$(argument: any): ts.Expression { return ts.factory.createIdentifier(argument.getText().replace(/\$\$/, '')); } } else if (ts.isPropertyAccessExpression(argument)) { - return ts.factory.updatePropertyAccessExpression - (argument, updateArgumentFor$$(argument.expression), argument.name); + return ts.factory.updatePropertyAccessExpression( + argument, updateArgumentFor$$(argument.expression), argument.name); } } function addComponentAttr(temp: any, node: ts.Identifier, lastStatement: any, statements: ts.Statement[], identifierNode: ts.Identifier, log: LogInfo[], - isStylesAttr: boolean): void { + isStylesAttr: boolean, isGlobalStyles: boolean): void { const propName: string = node.getText(); if (propName === ATTRIBUTE_ANIMATION) { if (!lastStatement.statement) { @@ -595,15 +755,18 @@ function addComponentAttr(temp: any, node: ts.Identifier, lastStatement: any, } else if (GLOBAL_STYLE_FUNCTION.has(propName) || INNER_STYLE_FUNCTION.has(propName)) { const styleBlock: ts.Block = GLOBAL_STYLE_FUNCTION.get(propName) || INNER_STYLE_FUNCTION.get(propName); - bindComponentAttr(styleBlock.statements[0] as ts.ExpressionStatement, identifierNode, - statements, log, false, true); + if (GLOBAL_STYLE_FUNCTION.has(propName)) { + bindComponentAttr(styleBlock.statements[0] as ts.ExpressionStatement, identifierNode, + statements, log, false, true, true); + } else { + bindComponentAttr(styleBlock.statements[0] as ts.ExpressionStatement, identifierNode, + statements, log, false, true, false); + } lastStatement.kind = true; - } else if (propName === BIND_POPUP && temp.arguments.length === 2 && - temp.arguments[0].getText().match(/^\$\$(.|\n)+/)) { + } else if (!isStylesAttr && [BIND_POPUP, CHECKED].includes(propName) && + temp.arguments.length && temp.arguments[0] && temp.arguments[0].getText().match(/^\$\$(.|\n)+/)) { const argumentsArr: ts.Expression[] = []; - const varExp: ts.Expression = updateArgumentFor$$(temp.arguments[0]); - argumentsArr.push(generateObjectFor$$(varExp)); - argumentsArr.push(temp.arguments[1]); + classifyArgumentsNum(temp.arguments, argumentsArr, propName, identifierNode); statements.push(ts.factory.createExpressionStatement( createFunction(identifierNode, node, argumentsArr))); lastStatement.kind = true; @@ -612,8 +775,10 @@ function addComponentAttr(temp: any, node: ts.Identifier, lastStatement: any, if (!COMMON_ATTRS.has(propName)) { validateStateStyleSyntax(temp, log); } - for (let i=0; i, parentComponentN const deleteParamsStatements: ts.PropertyDeclaration[] = []; const checkController: ControllerType = { hasController: !componentCollection.customDialogs.has(parentComponentName.getText()) }; + const interfaceNode = ts.factory.createInterfaceDeclaration(undefined, undefined, + parentComponentName.getText() + INTERFACE_NAME_SUFFIX, undefined, undefined, []); members.forEach((item: ts.ClassElement) => { let updateItem: ts.ClassElement; if (ts.isPropertyDeclaration(item)) { + addPropertyMember(item, newMembers, program); const result: UpdateResult = processMemberVariableDecorators(parentComponentName, item, - ctorNode, watchMap, checkController, log, program, context, hasPreview); + ctorNode, watchMap, checkController, log, program, context, hasPreview, interfaceNode); if (result.isItemUpdate()) { updateItem = result.getProperity(); } else { @@ -146,14 +163,74 @@ function processMembers(members: ts.NodeArray, parentComponentN newMembers.push(updateItem); } }); + INTERFACE_NODE_SET.add(interfaceNode); validateBuildMethodCount(buildCount, parentComponentName, log); validateHasController(parentComponentName, checkController, log); newMembers.unshift(addDeleteParamsFunc(deleteParamsStatements)); - newMembers.unshift(addUpdateParamsFunc(updateParamsStatements)); - newMembers.unshift(addConstructor(ctorNode, watchMap)); + newMembers.unshift(addUpdateParamsFunc(updateParamsStatements, parentComponentName)); + newMembers.unshift(addConstructor(ctorNode, watchMap, parentComponentName)); return newMembers; } +function addPropertyMember(item: ts.ClassElement, newMembers: ts.ClassElement[], + program: ts.Program):void { + const propertyItem: ts.PropertyDeclaration = item as ts.PropertyDeclaration; + let decoratorName: string; + let updatePropertyItem: ts.PropertyDeclaration; + const type: ts.TypeNode = propertyItem.type; + if (!propertyItem.decorators || propertyItem.decorators.length === 0) { + updatePropertyItem = createPropertyDeclaration(propertyItem, type, true); + newMembers.push(updatePropertyItem); + } else if (propertyItem.decorators) { + for (let i = 0; i < propertyItem.decorators.length; i++) { + let newType: ts.TypeNode; + decoratorName = propertyItem.decorators[i].getText().replace(/\(.*\)$/, '').trim(); + switch (decoratorName) { + case COMPONENT_STATE_DECORATOR: + case COMPONENT_PROVIDE_DECORATOR: + newType = ts.factory.createTypeReferenceNode(isSimpleType(type, program) ? + OBSERVED_PROPERTY_SIMPLE : OBSERVED_PROPERTY_OBJECT, [type]); + break; + case COMPONENT_LINK_DECORATOR: + case COMPONENT_CONSUME_DECORATOR: + newType = ts.factory.createTypeReferenceNode(isSimpleType(type, program) ? + SYNCHED_PROPERTY_SIMPLE_TWO_WAY : SYNCHED_PROPERTY_SIMPLE_ONE_WAY, [type]); + break; + case COMPONENT_PROP_DECORATOR: + newType = ts.factory.createTypeReferenceNode(SYNCHED_PROPERTY_SIMPLE_ONE_WAY, [type]); + break; + case COMPONENT_OBJECT_LINK_DECORATOR: + newType = ts.factory.createTypeReferenceNode(SYNCHED_PROPERTY_NESED_OBJECT, [type]); + break; + case COMPONENT_STORAGE_PROP_DECORATOR: + case COMPONENT_STORAGE_LINK_DECORATOR: + newType = ts.factory.createTypeReferenceNode(OBSERVED_PROPERTY_ABSTRACT, [type]); + break; + } + updatePropertyItem = createPropertyDeclaration(propertyItem, newType, false); + if (updatePropertyItem) { + newMembers.push(updatePropertyItem); + } + } + } +} + +function createPropertyDeclaration(propertyItem: ts.PropertyDeclaration, newType: ts.TypeNode | undefined, + normalVar: boolean): ts.PropertyDeclaration { + if (typeof newType === undefined) { + return undefined; + } + let prefix: string = ''; + if (!normalVar) { + prefix = '__'; + } + const privateM: ts.ModifierToken = + ts.factory.createModifier(ts.SyntaxKind.PrivateKeyword); + return ts.factory.updatePropertyDeclaration(propertyItem, undefined, + propertyItem.modifiers || [privateM], prefix + propertyItem.name.getText(), + propertyItem.questionToken, newType, undefined); +} + function processComponentMethod(node: ts.MethodDeclaration, parentComponentName: ts.Identifier, context: ts.TransformationContext, log: LogInfo[], buildCount: BuildCount): ts.MethodDeclaration { let updateItem: ts.MethodDeclaration = node; @@ -212,9 +289,6 @@ function processBuildMember(node: ts.MethodDeclaration, context: ts.Transformati const buildNode: ts.MethodDeclaration = processComponentBuild(node, log); return ts.visitNode(buildNode, visitBuild); function visitBuild(node: ts.Node): ts.Node { - if (isCustomComponentNode(node)) { - return node; - } if (isGeometryView(node)) { node = processGeometryView(node as ts.ExpressionStatement, log); } @@ -229,80 +303,10 @@ function processBuildMember(node: ts.MethodDeclaration, context: ts.Transformati ts.factory.createIdentifier(FOREACH_OBSERVED_OBJECT), ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [node]); } - if ((ts.isIdentifier(node) || ts.isPropertyAccessExpression(node)) && - validateBuilderFunctionNode(node)) { - return getParsedBuilderAttrArgument(node); - } return ts.visitEachChild(node, visitBuild, context); } } -function validateBuilderFunctionNode(node: ts.PropertyAccessExpression | ts.Identifier): boolean { - if (((ts.isPropertyAccessExpression(node) && node.expression && node.name && - node.expression.kind === ts.SyntaxKind.ThisKeyword && ts.isIdentifier(node.name) && - CUSTOM_BUILDER_METHOD.has(node.name.escapedText.toString())) || - ts.isIdentifier(node) && CUSTOM_BUILDER_METHOD.has(node.escapedText.toString())) && - !((ts.isPropertyAccessExpression(node) && validateBuilderParam(node)) || - (ts.isIdentifier(node) && node.parent && ts.isPropertyAccessExpression(node.parent) && - validateBuilderParam(node.parent)))) { - return true; - } else { - return false; - } -} - -function validateBuilderParam(node: ts.PropertyAccessExpression): boolean { - if (node.parent && ts.isCallExpression(node.parent) && node.parent.expression === node) { - return true; - } else { - return false; - } -} - -function getParsedBuilderAttrArgument(node: ts.PropertyAccessExpression | ts.Identifier): - ts.ObjectLiteralExpression { - let newObjectNode: ts.ObjectLiteralExpression = null; - if (ts.isPropertyAccessExpression(node)) { - newObjectNode = ts.factory.createObjectLiteralExpression([ - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier(BUILDER_ATTR_NAME), - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - node, - ts.factory.createIdentifier(BUILDER_ATTR_BIND) - ), - undefined, - [ts.factory.createThis()] - ) - ) - ]); - } else if (ts.isIdentifier(node)) { - newObjectNode = ts.factory.createObjectLiteralExpression([ - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier(BUILDER_ATTR_NAME), - node - ) - ]) - } - return newObjectNode; -} - -function isCustomComponentNode(node:ts.NewExpression | ts.ExpressionStatement): boolean { - if ((ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.escapedText - && componentCollection.customComponents.has(node.expression.escapedText.toString())) || - // @ts-ignore - (ts.isExpressionStatement(node) && node.expression && node.expression.expression && - // @ts-ignore - node.expression.expression.expression && node.expression.expression.expression.escapedText && - // @ts-ignore - node.expression.expression.expression.escapedText.toString().startsWith( - CUSTOM_COMPONENT_EARLIER_CREATE_CHILD))) { - return true; - }else { - return false; - } -} - function isGeometryView(node: ts.Node): boolean { if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) { const call: ts.CallExpression = node.expression; @@ -436,8 +440,9 @@ function processAnimateTo(node: ts.CallExpression): ts.CallExpression { node.typeArguments, node.arguments); } -function addUpdateParamsFunc(statements: ts.Statement[]): ts.MethodDeclaration { - return createParamsInitBlock(COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, statements); +function addUpdateParamsFunc(statements: ts.Statement[], parentComponentName: ts.Identifier): + ts.MethodDeclaration { + return createParamsInitBlock(COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, statements, parentComponentName); } function addDeleteParamsFunc(statements: ts.PropertyDeclaration[]): ts.MethodDeclaration { @@ -467,13 +472,17 @@ function addDeleteParamsFunc(statements: ts.PropertyDeclaration[]): ts.MethodDec return deleteParamsMethod; } -function createParamsInitBlock(express: string, statements: ts.Statement[]): ts.MethodDeclaration { +function createParamsInitBlock(express: string, statements: ts.Statement[], + parentComponentName?: ts.Identifier): ts.MethodDeclaration { const methodDeclaration: ts.MethodDeclaration = ts.factory.createMethodDeclaration(undefined, undefined, undefined, ts.factory.createIdentifier(express), undefined, undefined, [ts.factory.createParameterDeclaration(undefined, undefined, undefined, express === COMPONENT_CONSTRUCTOR_DELETE_PARAMS ? undefined : - ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), undefined, undefined, undefined)], - undefined, ts.factory.createBlock(statements, true)); + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), undefined, + express === COMPONENT_CONSTRUCTOR_DELETE_PARAMS ? undefined : + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier(parentComponentName.getText() + INTERFACE_NAME_SUFFIX), undefined), + undefined)], undefined, ts.factory.createBlock(statements, true)); return methodDeclaration; } diff --git a/compiler/src/process_component_constructor.ts b/compiler/src/process_component_constructor.ts index fa619f0..36f56ba 100644 --- a/compiler/src/process_component_constructor.ts +++ b/compiler/src/process_component_constructor.ts @@ -20,7 +20,9 @@ import { COMPONENT_CONSTRUCTOR_PARENT, COMPONENT_CONSTRUCTOR_PARAMS, COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, - COMPONENT_WATCH_FUNCTION + COMPONENT_WATCH_FUNCTION, + BASE_COMPONENT_NAME, + INTERFACE_NAME_SUFFIX } from './pre_define'; export function getInitConstructor(members: ts.NodeArray): ts.ConstructorDeclaration { @@ -35,7 +37,8 @@ export function getInitConstructor(members: ts.NodeArray): ts.Construct export function updateConstructor(ctorNode: ts.ConstructorDeclaration, para: ts.ParameterDeclaration[], addStatements: ts.Statement[], - isSuper: boolean = false): ts.ConstructorDeclaration { + isSuper: boolean = false, isAdd: boolean = false, parentComponentName?: ts.Identifier): + ts.ConstructorDeclaration { let modifyPara: ts.ParameterDeclaration[]; if (para && para.length) { modifyPara = Array.from(ctorNode.parameters); @@ -55,15 +58,50 @@ export function updateConstructor(ctorNode: ts.ConstructorDeclaration, } } if (ctorNode) { + let ctorPara: ts.ParameterDeclaration[] | ts.NodeArray = + modifyPara || ctorNode.parameters; + if (isAdd) { + ctorPara = addParamsType(ctorNode, modifyPara, parentComponentName); + } ctorNode = ts.factory.updateConstructorDeclaration(ctorNode, ctorNode.decorators, - ctorNode.modifiers, modifyPara || ctorNode.parameters, - ts.factory.createBlock(modifyBody || ctorNode.body.statements, true)); + ctorNode.modifiers, ctorPara, ts.factory.createBlock(modifyBody || ctorNode.body.statements, true)); } return ctorNode; } -export function addConstructor(ctorNode: any, watchMap: Map) - : ts.ConstructorDeclaration { +function addParamsType(ctorNode: ts.ConstructorDeclaration, modifyPara: ts.ParameterDeclaration[], + parentComponentName: ts.Identifier): ts.ParameterDeclaration[] { + const tsPara: ts.ParameterDeclaration[] | ts.NodeArray = + modifyPara || ctorNode.parameters; + const newTSPara: ts.ParameterDeclaration[] = []; + tsPara.forEach((item) => { + let parameter: ts.ParameterDeclaration = item; + switch (item.getText()) { + case COMPONENT_CONSTRUCTOR_ID + '?': + parameter = ts.factory.updateParameterDeclaration(item, item.decorators, item.modifiers, + item.dotDotDotToken, item.name, item.questionToken, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), item.initializer); + break; + case COMPONENT_CONSTRUCTOR_PARENT + '?': + parameter = ts.factory.createParameterDeclaration(item.decorators, item.modifiers, + item.dotDotDotToken, item.name, item.questionToken, + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(BASE_COMPONENT_NAME), undefined), + item.initializer); + break; + case COMPONENT_CONSTRUCTOR_PARAMS + '?': + parameter = ts.factory.updateParameterDeclaration(item, item.decorators, item.modifiers, + item.dotDotDotToken, item.name, item.questionToken, + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier( + parentComponentName.getText() + INTERFACE_NAME_SUFFIX), undefined), item.initializer); + break; + } + newTSPara.push(parameter); + }); + return newTSPara; +} + +export function addConstructor(ctorNode: any, watchMap: Map, + parentComponentName: ts.Identifier): ts.ConstructorDeclaration { const watchStatements: ts.ExpressionStatement[] = []; watchMap.forEach((value, key) => { const watchNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( @@ -77,7 +115,7 @@ export function addConstructor(ctorNode: any, watchMap: Map) ts.factory.createStringLiteral(key), ts.isStringLiteral(value) ? ts.factory.createPropertyAccessExpression(ts.factory.createThis(), - ts.factory.createIdentifier(value.text)) : value as ts.PropertyAccessExpression + ts.factory.createIdentifier(value.text)) : value as ts.PropertyAccessExpression ] )); watchStatements.push(watchNode); @@ -91,5 +129,5 @@ export function addConstructor(ctorNode: any, watchMap: Map) ts.factory.createThis(), ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UPDATE_PARAMS)), undefined, [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS)])); return updateConstructor(updateConstructor(ctorNode, [], [callSuperStatement], true), [], - [updateWithValueParamsStatement, ...watchStatements], false); + [updateWithValueParamsStatement, ...watchStatements], false, true, parentComponentName); } diff --git a/compiler/src/process_component_member.ts b/compiler/src/process_component_member.ts index 1dc9b50..77ddab7 100644 --- a/compiler/src/process_component_member.ts +++ b/compiler/src/process_component_member.ts @@ -104,7 +104,7 @@ export const setUpdateParamsDecorators: Set = ]); export const immutableDecorators: Set = - new Set([COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]); + new Set([COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR, COMPONENT_BUILDERPARAM_DECORATOR]); export const simpleTypes: Set = new Set([ts.SyntaxKind.StringKeyword, ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.EnumDeclaration]); @@ -197,8 +197,8 @@ export const curPropMap: Map = new Map(); export function processMemberVariableDecorators(parentName: ts.Identifier, item: ts.PropertyDeclaration, ctorNode: ts.ConstructorDeclaration, watchMap: Map, - checkController: ControllerType, log: LogInfo[], program: ts.Program, - context: ts.TransformationContext, hasPreview: boolean): UpdateResult { + checkController: ControllerType, log: LogInfo[], program: ts.Program, context: ts.TransformationContext, + hasPreview: boolean, interfaceNode: ts.InterfaceDeclaration): UpdateResult { const updateResult: UpdateResult = new UpdateResult(); const name: ts.Identifier = item.name as ts.Identifier; if (!item.decorators || !item.decorators.length) { @@ -206,14 +206,15 @@ export function processMemberVariableDecorators(parentName: ts.Identifier, updateResult.setProperity(undefined); updateResult.setUpdateParams(createUpdateParams(name, COMPONENT_NON_DECORATOR)); updateResult.setCtor(updateConstructor(ctorNode, [], [ - createVariableInitStatement(item, COMPONENT_NON_DECORATOR, log, program, context, hasPreview)])); + createVariableInitStatement(item, COMPONENT_NON_DECORATOR, log, program, context, hasPreview, + interfaceNode)])); updateResult.setControllerSet(createControllerSet(item, parentName, name, checkController)); } else if (!item.type) { validatePropertyNonType(name, log); return updateResult; } else { processPropertyNodeDecorator(parentName, item, updateResult, ctorNode, name, watchMap, - log, program, context, hasPreview); + log, program, context, hasPreview, interfaceNode); } return updateResult; } @@ -239,7 +240,8 @@ function createControllerSet(node: ts.PropertyDeclaration, componentName: ts.Ide function processPropertyNodeDecorator(parentName: ts.Identifier, node: ts.PropertyDeclaration, updateResult: UpdateResult, ctorNode: ts.ConstructorDeclaration, name: ts.Identifier, watchMap: Map, log: LogInfo[], program: ts.Program, - context: ts.TransformationContext, hasPreview: boolean): void { + context: ts.TransformationContext, hasPreview: boolean, interfaceNode: ts.InterfaceDeclaration): + void { let stateManagementDecoratorCount: number = 0; for (let i = 0; i < node.decorators.length; i++) { const decoratorName: string = node.decorators[i].getText().replace(/\(.*\)$/, '').trim(); @@ -277,7 +279,8 @@ function processPropertyNodeDecorator(parentName: ts.Identifier, node: ts.Proper processWatch(node, node.decorators[i], watchMap, log); } else if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { stateManagementDecoratorCount += 1; - processStateDecorators(node, decoratorName, updateResult, ctorNode, log, program, context, hasPreview); + processStateDecorators(node, decoratorName, updateResult, ctorNode, log, program, context, + hasPreview, interfaceNode); } } if (stateManagementDecoratorCount > 1) { @@ -288,25 +291,28 @@ function processPropertyNodeDecorator(parentName: ts.Identifier, node: ts.Proper function processStateDecorators(node: ts.PropertyDeclaration, decorator: string, updateResult: UpdateResult, ctorNode: ts.ConstructorDeclaration, log: LogInfo[], - program: ts.Program, context: ts.TransformationContext, hasPreview:boolean): void { + program: ts.Program, context: ts.TransformationContext, hasPreview:boolean, + interfaceNode: ts.InterfaceDeclaration): void { const name: ts.Identifier = node.name as ts.Identifier; updateResult.setProperity(undefined); const updateState: ts.Statement[] = []; const variableInitStatement: ts.Statement = - createVariableInitStatement(node, decorator, log, program, context, hasPreview); + createVariableInitStatement(node, decorator, log, program, context, hasPreview, interfaceNode); if (variableInitStatement) { updateState.push(variableInitStatement); } addAddProvidedVar(node, name, decorator, updateState); updateResult.setCtor(updateConstructor(ctorNode, [], [...updateState], false)); - updateResult.setVariableGet(createGetAccessor(name, CREATE_GET_METHOD)); + if (decorator !== COMPONENT_BUILDERPARAM_DECORATOR) { + updateResult.setVariableGet(createGetAccessor(name, CREATE_GET_METHOD)); + updateResult.setDeleteParams(true); + } if (!immutableDecorators.has(decorator)) { - updateResult.setVariableSet(createSetAccessor(name, CREATE_SET_METHOD)); + updateResult.setVariableSet(createSetAccessor(name, CREATE_SET_METHOD, node.type)); } if (setUpdateParamsDecorators.has(decorator)) { updateResult.setUpdateParams(createUpdateParams(name, decorator)); } - updateResult.setDeleteParams(true); } function processWatch(node: ts.PropertyDeclaration, decorator: ts.Decorator, @@ -344,8 +350,8 @@ function processWatch(node: ts.PropertyDeclaration, decorator: ts.Decorator, } function createVariableInitStatement(node: ts.PropertyDeclaration, decorator: string, - log: LogInfo[], program: ts.Program, context: ts.TransformationContext, hasPreview: boolean): - ts.Statement { + log: LogInfo[], program: ts.Program, context: ts.TransformationContext, hasPreview: boolean, + interfaceNode: ts.InterfaceDeclaration): ts.Statement { const name: ts.Identifier = node.name as ts.Identifier; let type: ts.TypeNode; let updateState: ts.ExpressionStatement; @@ -382,6 +388,12 @@ function createVariableInitStatement(node: ts.PropertyDeclaration, decorator: st updateState = updateConsumeProperty(node, name); break; } + const members = interfaceNode.members; + members.push(ts.factory.createPropertySignature(undefined, name, + ts.factory.createToken(ts.SyntaxKind.QuestionToken), type)); + interfaceNode = ts.factory.updateInterfaceDeclaration(interfaceNode, undefined, + interfaceNode.modifiers, interfaceNode.name, interfaceNode.typeParameters, + interfaceNode.heritageClauses, members); return updateState; } @@ -417,9 +429,9 @@ function createUpdateParams(name: ts.Identifier, decorator: string): ts.Statemen builderParamObjectCollection.set(componentCollection.currentClassName, new Set([])); } builderParamObjectCollection.get(componentCollection.currentClassName) - .add(name.escapedText.toString()) + .add(name.escapedText.toString()); } - updateParamsNode = createUpdateParamsWithoutIf(name, 1); + updateParamsNode = createUpdateParamsWithoutIf(name); break; } return updateParamsNode; @@ -435,15 +447,9 @@ function createUpdateParamsWithIf(name: ts.Identifier): ts.IfStatement { createUpdateParamsWithoutIf(name)], true), undefined); } -function createUpdateParamsWithoutIf(name: ts.Identifier, isAdd: number = 0): ts.ExpressionStatement { - let textName: string; - if (isAdd) { - textName = `__${name.getText()}`; - } else { - textName = name.getText(); - } +function createUpdateParamsWithoutIf(name: ts.Identifier): ts.ExpressionStatement { return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( - createPropertyAccessExpressionWithThis(textName), + createPropertyAccessExpressionWithThis(name.getText()), ts.factory.createToken(ts.SyntaxKind.EqualsToken), createPropertyAccessExpressionWithParams(name.getText()))); } @@ -711,11 +717,12 @@ function createGetAccessor(item: ts.Identifier, express: string): ts.GetAccessor return getAccessorStatement; } -function createSetAccessor(item: ts.Identifier, express: string): ts.SetAccessorDeclaration { +function createSetAccessor(item: ts.Identifier, express: string, type: ts.TypeNode): + ts.SetAccessorDeclaration { const setAccessorStatement: ts.SetAccessorDeclaration = ts.factory.createSetAccessorDeclaration(undefined, undefined, item, [ts.factory.createParameterDeclaration(undefined, undefined, undefined, - ts.factory.createIdentifier(CREATE_NEWVALUE_IDENTIFIER), undefined, undefined, + ts.factory.createIdentifier(CREATE_NEWVALUE_IDENTIFIER), undefined, type, undefined)], ts.factory.createBlock([ts.factory.createExpressionStatement( ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( createPropertyAccessExpressionWithThis(`__${item.getText()}`), @@ -732,7 +739,7 @@ function isForbiddenUseStateType(typeNode: ts.TypeNode): boolean { return false; } -function isSimpleType(typeNode: ts.TypeNode, program: ts.Program): boolean { +export function isSimpleType(typeNode: ts.TypeNode, program: ts.Program): boolean { let checker: ts.TypeChecker; if (program) { checker = program.getTypeChecker(); diff --git a/compiler/src/process_custom_component.ts b/compiler/src/process_custom_component.ts index 5ac3f84..34ca61d 100644 --- a/compiler/src/process_custom_component.ts +++ b/compiler/src/process_custom_component.ts @@ -52,6 +52,7 @@ import { createViewCreate, createCustomComponentNewExpression } from './process_component_member'; +import { builderParamObjectCollection } from './process_component_member'; import { LogType, LogInfo, @@ -72,10 +73,50 @@ const decoractorMap: Map>> = new Map( export function processCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], log: LogInfo[]): void { if (ts.isCallExpression(node.expression)) { - addCustomComponent(node, newStatements, createCustomComponentNewExpression(node.expression), log); + let ischangeNode: boolean = false; + let customComponentNewExpression: ts.NewExpression = createCustomComponentNewExpression( + node.expression); + let argumentsArray: ts.PropertyAssignment[]; + if (isHasChild(node.expression)) { + // @ts-ignore + argumentsArray = node.expression.arguments[0].properties.slice(); + argumentsArray.forEach((item: ts.PropertyAssignment, index: number) => { + if (isToChange(item, node.expression as ts.CallExpression)) { + ischangeNode = true; + const propertyAssignmentNode: ts.PropertyAssignment = ts.factory.updatePropertyAssignment( + item, item.name, changeNodeFromCallToArrow(item.initializer as ts.CallExpression)); + argumentsArray.splice(index, 1, propertyAssignmentNode); + } + }); + if (ischangeNode) { + const newNode: ts.ExpressionStatement = ts.factory.updateExpressionStatement(node, + ts.factory.createNewExpression(node.expression.expression, node.expression.typeArguments, + [ts.factory.createObjectLiteralExpression(argumentsArray, true)])); + customComponentNewExpression = createCustomComponentNewExpression( + newNode.expression as ts.CallExpression); + } + } + addCustomComponent(node, newStatements, customComponentNewExpression, log); } } +function isHasChild(node: ts.CallExpression): boolean { + return node.arguments && node.arguments[0] && ts.isObjectLiteralExpression(node.arguments[0]) && + node.arguments[0].properties && node.arguments[0].properties.length; +} + +function isToChange(item: ts.PropertyAssignment, node: ts.CallExpression): boolean { + const builderParamName: Set = builderParamObjectCollection.get(node.expression.getText()); + return item.initializer && ts.isCallExpression(item.initializer) && builderParamName && + builderParamName.has(item.name.getText()); +} + +function changeNodeFromCallToArrow(node: ts.CallExpression): ts.ArrowFunction { + return ts.factory.createArrowFunction(undefined, undefined, [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + ts.factory.createBlock([ts.factory.createExpressionStatement(node)], true)); +} + function addCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], newNode: ts.NewExpression, log: LogInfo[]): void { if (ts.isNewExpression(newNode)) { @@ -89,7 +130,7 @@ function addCustomComponent(node: ts.ExpressionStatement, newStatements: ts.Stat function addCustomComponentStatements(node: ts.ExpressionStatement, newStatements: ts.Statement[], newNode: ts.NewExpression, name: string, props: ts.ObjectLiteralElementLike[]): void { const id: string = componentInfo.id.toString(); - newStatements.push(createFindChildById(id), createCustomComponentIfStatement(id, + newStatements.push(createFindChildById(id, name), createCustomComponentIfStatement(id, ts.factory.updateExpressionStatement(node, createViewCreate(newNode)), ts.factory.createObjectLiteralExpression(props, true), name)); } @@ -105,13 +146,16 @@ function validateCustomComponentPrams(node: ts.ExpressionStatement, name: string ts.isObjectLiteralExpression(nodeArguments[0])) { const nodeArgument: ts.ObjectLiteralExpression = nodeArguments[0] as ts.ObjectLiteralExpression; nodeArgument.properties.forEach(item => { - if (item.name && item.name.escapedText) { - // @ts-ignore + if (item.name && ts.isIdentifier(item.name)) { curChildProps.add(item.name.escapedText.toString()); } if (isThisProperty(item, propertySet)) { validateStateManagement(item, name, log); if (isNonThisProperty(item, linkSet)) { + if (isToChange(item as ts.PropertyAssignment, node.expression as ts.CallExpression)) { + item = ts.factory.updatePropertyAssignment(item as ts.PropertyAssignment, + item.name, changeNodeFromCallToArrow(item.initializer)); + } props.push(item); } } else { @@ -119,6 +163,7 @@ function validateCustomComponentPrams(node: ts.ExpressionStatement, name: string } }); } + validateMandatoryToAssignmentViaParam(node, name, curChildProps, log); validateMandatoryToInitViaParam(node, name, curChildProps, log); } @@ -272,13 +317,14 @@ function getPropertyDecoratorKind(propertyName: string, customComponentName: str } } -function createFindChildById(id: string): ts.VariableStatement { +function createFindChildById(id: string, name: string): ts.VariableStatement { return ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList( [ts.factory.createVariableDeclaration(ts.factory.createIdentifier( - `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`), undefined, undefined, - ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createThis(), - ts.factory.createIdentifier(`${CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID}`)), undefined, - [ts.factory.createStringLiteral(id)]))], ts.NodeFlags.Let)); + `${CUSTOM_COMPONENT_EARLIER_CREATE_CHILD}${id}`), undefined, ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier(name)), ts.factory.createAsExpression(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier( + `${CUSTOM_COMPONENT_FUNCTION_FIND_CHILD_BY_ID}`)), undefined, [ts.factory.createStringLiteral(id)]), + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(name))))], ts.NodeFlags.Let)); } function createCustomComponentIfStatement(id: string, node: ts.ExpressionStatement, @@ -346,11 +392,27 @@ function validateNonExistentProperty(node: ts.ObjectLiteralElementLike, customComponentName: string, log: LogInfo[]): void { log.push({ type: LogType.ERROR, - message: `Property '${node.name.getText()}' does not exist on type '${customComponentName}'.`, + message: `Property '${node.name.escapedText.toString()}' does not exist on type '${customComponentName}'.`, pos: node.name.getStart() }); } +function validateMandatoryToAssignmentViaParam(node: ts.ExpressionStatement, customComponentName: string, + curChildProps: Set, log: LogInfo[]): void { + if (builderParamObjectCollection.get(customComponentName) && + builderParamObjectCollection.get(customComponentName).size) { + builderParamObjectCollection.get(customComponentName).forEach((item) => { + if (!curChildProps.has(item)) { + log.push({ + type: LogType.ERROR, + message: `The property decorated with @BuilderParam '${item}' must be assigned a value .`, + pos: node.getStart() + }); + } + }); + } +} + function validateMandatoryToInitViaParam(node: ts.ExpressionStatement, customComponentName: string, curChildProps: Set, log: LogInfo[]): void { const mandatoryToInitViaParamSet: Set = new Set([ diff --git a/compiler/src/process_ui_syntax.ts b/compiler/src/process_ui_syntax.ts index 481074b..27a65a0 100644 --- a/compiler/src/process_ui_syntax.ts +++ b/compiler/src/process_ui_syntax.ts @@ -56,7 +56,8 @@ import { EXTEND_ATTRIBUTE, JS_BIND_COMPONENTS, INNER_STYLE_FUNCTION, - GLOBAL_STYLE_FUNCTION + GLOBAL_STYLE_FUNCTION, + INTERFACE_NODE_SET } from './component_map'; import { resources } from '../main'; @@ -81,8 +82,14 @@ export function processUISyntax(program: ts.Program, ut = false): Function { node = ts.visitEachChild(node, processAllNodes, context); GLOBAL_STYLE_FUNCTION.forEach((block, styleName) => { BUILDIN_STYLE_NAMES.delete(styleName); - }) + }); GLOBAL_STYLE_FUNCTION.clear(); + const statements: ts.NodeArray = node.statements; + INTERFACE_NODE_SET.forEach(item => { + statements.unshift(item); + }); + node = ts.factory.updateSourceFile(node, statements); + INTERFACE_NODE_SET.clear(); return node; } else { return node; @@ -98,7 +105,7 @@ export function processUISyntax(program: ts.Program, ut = false): Function { componentCollection.currentClassName = null; INNER_STYLE_FUNCTION.forEach((block, styleName) => { BUILDIN_STYLE_NAMES.delete(styleName); - }) + }); INNER_STYLE_FUNCTION.clear(); } else if (ts.isFunctionDeclaration(node)) { if (hasDecorator(node, COMPONENT_EXTEND_DECORATOR)) { @@ -154,7 +161,7 @@ function collectComponents(node: ts.SourceFile): void { // @ts-ignore if (node.identifiers && node.identifiers.size) { // @ts-ignore - for (let key of node.identifiers.keys()) { + for (const key of node.identifiers.keys()) { if (JS_BIND_COMPONENTS.has(key)) { appComponentCollection.add(key); } @@ -186,7 +193,7 @@ function processResourceData(node: ts.CallExpression): ts.Node { return node; } -function createResourceParam(resourceValue: number, resourceType: number,argsArr: ts.Expression[]): +function createResourceParam(resourceValue: number, resourceType: number, argsArr: ts.Expression[]): ts.ObjectLiteralExpression { const resourceParams: ts.ObjectLiteralExpression = ts.factory.createObjectLiteralExpression( [ @@ -384,7 +391,7 @@ function isPropertyExist(typeChecker: ts.TypeChecker, pos: number, type: ts.Type } else { transformLog.errors.push({ type: LogType.ERROR, - message: `TS2339: Property '${propertyName}' does not exist on type '${componentName}'.`, + message: `TS2339: Property '${propertyName}' does not exist on type '${componentName}Attribute'.`, pos: pos }); } diff --git a/compiler/src/result_process.ts b/compiler/src/result_process.ts index 46ea792..fb0269a 100644 --- a/compiler/src/result_process.ts +++ b/compiler/src/result_process.ts @@ -14,7 +14,6 @@ */ import ts from 'typescript'; -import path from 'path'; import { BUILD_OFF } from './pre_define'; import { diff --git a/compiler/src/utils.ts b/compiler/src/utils.ts index fdd8cf4..c7a7700 100644 --- a/compiler/src/utils.ts +++ b/compiler/src/utils.ts @@ -159,10 +159,10 @@ export function createFunction(node: ts.Identifier, attrNode: ts.Identifier, } export function circularFile(inputPath: string, outputPath: string): void { - if ((!inputPath) || (!outputPath)) { + if (!inputPath || !outputPath) { return; } - fs.readdir(inputPath, function (err, files) { + fs.readdir(inputPath, function(err, files) { if (!files) { return; } @@ -205,4 +205,4 @@ function mkDir(path_: string): void { mkDir(parent); } fs.mkdirSync(path_); -} \ No newline at end of file +} diff --git a/compiler/src/validate_ui_syntax.ts b/compiler/src/validate_ui_syntax.ts index 7d1f305..5dd4474 100644 --- a/compiler/src/validate_ui_syntax.ts +++ b/compiler/src/validate_ui_syntax.ts @@ -41,9 +41,10 @@ import { COMPONENT_CONSTRUCTOR_ID, COMPONENT_CONSTRUCTOR_PARENT, COMPONENT_CONSTRUCTOR_PARAMS, - COMPONENT_EXTEND_DECORATOR, COMPONENT_OBSERVED_DECORATOR, - STYLES + STYLES, + VALIDATE_MODULE, + COMPONENT_BUILDER_DECORATOR } from './pre_define'; import { INNER_COMPONENT_NAMES, @@ -53,7 +54,8 @@ import { BUILDIN_STYLE_NAMES, EXTEND_ATTRIBUTE, GLOBAL_STYLE_FUNCTION, - STYLES_ATTRIBUTE + STYLES_ATTRIBUTE, + CUSTOM_BUILDER_METHOD } from './component_map'; import { LogType, @@ -183,19 +185,20 @@ function checkComponentDecorator(source: string, filePath: string, } 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()) + 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, log); + 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, log: LogInfo[]): void { + fileName: string, isPreview: boolean, checkEntry: boolean, log: LogInfo[]): void { if (result.previewCount > 10 && fileQuery === '?entry') { log.push({ type: LogType.ERROR, @@ -210,7 +213,7 @@ function validateEntryAndPreviewCount(result: DecoratorResult, fileQuery: string fileName: fileName }); } - if (isPreview && result.previewCount < 1 && result.entryCount !== 1 && + if (isPreview && !checkEntry && result.previewCount < 1 && result.entryCount !== 1 && fileQuery === '?entry') { log.push({ type: LogType.ERROR, @@ -218,7 +221,7 @@ function validateEntryAndPreviewCount(result: DecoratorResult, fileQuery: string + `decorator, or at least one '@Preview' decorator.`, fileName: fileName }); - } else if (!isPreview && result.entryCount !== 1 && fileQuery === '?entry') { + } else if ((!isPreview || isPreview && checkEntry) && result.entryCount !== 1 && fileQuery === '?entry') { log.push({ type: LogType.ERROR, message: `A page configured in 'config.json' must have one and only one '@Entry' ` @@ -323,6 +326,9 @@ function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponent if (ts.isClassDeclaration(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()); + } node.getChildren().forEach((item: ts.Node) => visitAllNode(item, sourceFileNode, allComponentNames, log)); } @@ -744,7 +750,7 @@ export function sourceReplace(source: string, sourcePath: string): ReplaceResult return { content: content, log: log - } + }; } export function preprocessExtend(content: string, sourcePath: string, log: LogInfo[]): string { @@ -772,7 +778,7 @@ export function preprocessExtend(content: string, sourcePath: string, log: LogIn syntaxCheckContent = content; } if (result.error_otherParsers) { - for(let i = 0; i < result.error_otherParsers.length; i++) { + for (let i = 0; i < result.error_otherParsers.length; i++) { log.push({ type: LogType.ERROR, message: result.error_otherParsers[i].errMessage, @@ -811,39 +817,53 @@ export function processSystemApi(content: string, isProcessWhiteList: boolean = } const REG_LIB_SO: RegExp = /import\s+(.+)\s+from\s+['"]lib(\S+)\.so['"]|import\s+(.+)\s*=\s*require\(\s*['"]lib(\S+)\.so['"]\s*\)/g; - return content.replace(REG_LIB_SO, (_, item1, item2, item3, item4) => { + const systemValueCollection: Set = new Set(); + const newContent: string = content.replace(REG_LIB_SO, (_, item1, item2, item3, item4) => { const libSoValue: string = item1 || item3; const libSoKey: string = item2 || item4; 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 (!isProcessWhiteList && validateWhiteListModule(moduleType, systemKey)) { - return item; - } else if (isProcessWhiteList) { - systemValue = item2; - moduleType = item4; - systemKey = item5; - } - 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}') ? ` + + }).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 (!isProcessWhiteList && validateWhiteListModule(moduleType, systemKey)) { + return item; + } else if (isProcessWhiteList) { + 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}') || ` + + } 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 item; + }); + return processInnerModule(newContent, systemValueCollection); } +function processInnerModule(content: string, systemValueCollection: Set): 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 validateWhiteListModule(moduleType: string, systemKey: string): boolean { - return moduleType === 'ohos' && /^application\./g.test(systemKey); + return moduleType === 'ohos' && VALIDATE_MODULE_REG.test(systemKey); } export function resetComponentCollection() { diff --git a/compiler/test/ut/builder/builderBindPopu.ts b/compiler/test/ut/builder/builderBindPopu.ts new file mode 100644 index 0000000..8869428 --- /dev/null +++ b/compiler/test/ut/builder/builderBindPopu.ts @@ -0,0 +1,75 @@ +/* + * 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. + */ + +exports.source = ` + +@Entry +@Component +struct Banner { +@Builder textBuilder() { + Text("文本") + .fontSize(30) +} +@Builder NavigationTitle(label:string) { + Column() { + Text(label) + .width(10) + .bindMenu(this.textBuilder) + } +} + build() { + Column() { + Text("111") + .bindMenu(this.NavigationTitle("111")) + } + } +} +` +exports.expectResult = +`class Banner extends View { + constructor(compilerAssignedUniqueChildId, parent, params) { + super(compilerAssignedUniqueChildId, parent); + this.updateWithValueParams(params); + } + updateWithValueParams(params) { + } + aboutToBeDeleted() { + SubscriberManager.Get().delete(this.id()); + } + textBuilder() { + Text.create("文本"); + Text.fontSize(30); + Text.pop(); + } + NavigationTitle(label) { + Column.create(); + Text.create(label); + Text.width(10); + Text.bindMenu({ builder: this.textBuilder.bind(this) }); + Text.pop(); + Column.pop(); + } + render() { + Column.create(); + Text.create("111"); + Text.bindMenu({ builder: () => { + this.NavigationTitle("111"); + } }); + Text.pop(); + Column.pop(); + } +} +loadDocument(new Banner("1", undefined, {})); +` diff --git a/compiler/test/ut/builder/builderLambda.ts b/compiler/test/ut/builder/builderLambda.ts index c197350..56d695a 100644 --- a/compiler/test/ut/builder/builderLambda.ts +++ b/compiler/test/ut/builder/builderLambda.ts @@ -29,10 +29,10 @@ struct CustomContainer { } } -@Builder function specificParam(label1: string, label2: string) { +@Builder function specificParam() { Column() { - Text(label1) - Text(label2) + Text("label1") + Text("label2") } } @@ -47,7 +47,7 @@ struct CustomContainerUser { .width(50) Text("content2") } - specificParam("content3", "content4") + specificParam() } } } @@ -57,8 +57,8 @@ exports.expectResult = `class CustomContainer extends View { constructor(compilerAssignedUniqueChildId, parent, params) { super(compilerAssignedUniqueChildId, parent); - this.header = "" - this.footer = "" + this.header = ""; + this.footer = ""; this.updateWithValueParams(params); } updateWithValueParams(params) { @@ -68,18 +68,11 @@ exports.expectResult = if (params.footer !== undefined) { this.footer = params.footer; } - this.__child = params.child; + this.child = params.child; } aboutToBeDeleted() { - this.__child.aboutToBeDeleted(); SubscriberManager.Get().delete(this.id()); } - get child(){ - return this.__child.get(); - } - set child(newValue) { - this.__child.set(newValue) - } render() { Column.create(); Text.create(this.header); @@ -90,15 +83,15 @@ exports.expectResult = Column.pop(); } } -function specificParam(label1, label2) { +function specificParam() { Column.create(); - Text.create(label1); + Text.create("label1"); Text.pop(); - Text.create(label1); + Text.create("label2"); Text.pop(); Column.pop(); } -class CustomContainerUser { +class CustomContainerUser extends View { constructor(compilerAssignedUniqueChildId, parent, params) { super(compilerAssignedUniqueChildId, parent); this.updateWithValueParams(params); @@ -117,12 +110,12 @@ class CustomContainerUser { child: () => { Column.create(); Text.create("content1"); - Text.width(50) + Text.width(50); Text.pop(); Text.create("content2"); Text.pop(); Column.pop(); - specificParam("content3", "content4) + specificParam(); } })); } @@ -132,18 +125,18 @@ class CustomContainerUser { child: () => { Column.create(); Text.create("content1"); - Text.width(50) + Text.width(50); Text.pop(); Text.create("content2"); Text.pop(); Column.pop(); - specificParam("content3", "content4) - }} + specificParam(); + } }); View.create(earlierCreatedChild_2); } - Column.pop(); + Column.pop(); } } -loadDocument(new MyComponent("1", undefined, {})); +loadDocument(new CustomContainerUser("1", undefined, {})); ` diff --git a/compiler/test/ut/builder/builderParam.ts b/compiler/test/ut/builder/builderParam.ts index 49a3916..d21e640 100644 --- a/compiler/test/ut/builder/builderParam.ts +++ b/compiler/test/ut/builder/builderParam.ts @@ -14,35 +14,30 @@ */ exports.source = ` + @Component struct CustomContainer { header: string = ""; + menuInfo: () => any; footer: string = ""; @BuilderParam child: () => any; build() { Column() { - Text(this.header) this.child() + Text(this.header) Text(this.footer) } } } -@Builder function specificParam(label1: string, label2: string) { - Column() { - Text(label1) - Text(label2) - } -} - @Entry @Component struct CustomContainerUser { - @Builder specificChild() { + @Builder specificChild(label:string) { Column() { Text("My content1") - Text("My content1") + Text(label) } } @@ -51,12 +46,8 @@ struct CustomContainerUser { CustomContainer({ header: "Header", footer: "Footer", - child: this.specificChild - }) - CustomContainer({ - header: "Header", - footer: "Footer", - child: specificParam("content3", "content4") + menuInfo: this.specificChild("menuInfo"), + child: this.specificChild("child") }) } } @@ -66,48 +57,37 @@ exports.expectResult = `class CustomContainer extends View { constructor(compilerAssignedUniqueChildId, parent, params) { super(compilerAssignedUniqueChildId, parent); - this.header = "" - this.footer = "" + this.header = ""; + this.menuInfo = undefined; + this.footer = ""; this.updateWithValueParams(params); } updateWithValueParams(params) { if (params.header !== undefined) { this.header = params.header; } + if (params.menuInfo !== undefined) { + this.menuInfo = params.menuInfo; + } if (params.footer !== undefined) { this.footer = params.footer; } - this.__child = params.child; + this.child = params.child; } aboutToBeDeleted() { - this.__child.aboutToBeDeleted(); SubscriberManager.Get().delete(this.id()); } - get child(){ - return this.__child.get(); - } - set child(newValue) { - this.__child.set(newValue) - } render() { Column.create(); + this.child(); Text.create(this.header); Text.pop(); - this.child(); Text.create(this.footer); Text.pop(); Column.pop(); } } -function specificParam(label1, label2) { - Column.create(); - Text.create(label1); - Text.pop(); - Text.create(label1); - Text.pop(); - Column.pop(); -} -class CustomContainerUser { +class CustomContainerUser extends View { constructor(compilerAssignedUniqueChildId, parent, params) { super(compilerAssignedUniqueChildId, parent); this.updateWithValueParams(params); @@ -117,50 +97,40 @@ class CustomContainerUser { aboutToBeDeleted() { SubscriberManager.Get().delete(this.id()); } - specificChild() { + specificChild(label) { Column.create(); Text.create("My content1"); Text.pop(); - Text.create("My content2"); + Text.create(label); Text.pop(); Column.pop(); } render() { Column.create(); - let earlierCreatedChild_2 = this.findChildById("2"); - if (earlierCreatedChild_2 == undefined) { - View.create(new CustomContainer("2", this, { - header: "Header", - footer: "Footer", - child: this.specificChild - })); - } - else { - earlierCreatedChild_2.updateWithValueParams({ - header: "Header", - footer: "Footer", - child: this.specificChild - }); - View.create(earlierCreatedChild_2); - } let earlierCreatedChild_3 = this.findChildById("3"); if (earlierCreatedChild_3 == undefined) { View.create(new CustomContainer("3", this, { header: "Header", footer: "Footer", - child: specificParam("content3", "content4") + menuInfo: this.specificChild("menuInfo"), + child: () => { + this.specificChild("child"); + } })); } else { earlierCreatedChild_3.updateWithValueParams({ header: "Header", footer: "Footer", - child: specificParam("content3", "content4") + menuInfo: this.specificChild("menuInfo"), + child: () => { + this.specificChild("child"); + } }); View.create(earlierCreatedChild_3); } - Column.pop(); + Column.pop(); } } -loadDocument(new MyComponent("1", undefined, {})); +loadDocument(new CustomContainerUser("1", undefined, {})); ` diff --git a/compiler/test/ut/decorator/watchWithAnimateTo.ts b/compiler/test/ut/decorator/watchWithAnimateTo.ts index 7744147..6b8473d 100644 --- a/compiler/test/ut/decorator/watchWithAnimateTo.ts +++ b/compiler/test/ut/decorator/watchWithAnimateTo.ts @@ -33,9 +33,7 @@ struct MyAlertDialog { } build() { Stack() { - DialogView({dialogShow: $dialogVis}).onClick(() => { - animateTo({}, () => {}) - }) + DialogView({dialogShow: $dialogVis}) } } } @@ -108,15 +106,13 @@ class MyAlertDialog extends View { } render() { Stack.create(); - let earlierCreatedChild_2 = this.findChildById("2"); - if (earlierCreatedChild_2 == undefined) { - View.create(new (DialogView({ dialogShow: this.__dialogVis }).onClick)(() => { - Context.animateTo({}, () => { }); - })); + let earlierCreatedChild_3 = this.findChildById("3"); + if (earlierCreatedChild_3 == undefined) { + View.create(new DialogView("3", this, { dialogShow: this.__dialogVis })); } else { - earlierCreatedChild_2.updateWithValueParams({}); - View.create(earlierCreatedChild_2); + earlierCreatedChild_3.updateWithValueParams({}); + View.create(earlierCreatedChild_3); } Stack.pop(); } diff --git a/compiler/test/ut/syntacticSugar/$$.ts b/compiler/test/ut/syntacticSugar/$$.ts index ab6d9f7..35c9732 100644 --- a/compiler/test/ut/syntacticSugar/$$.ts +++ b/compiler/test/ut/syntacticSugar/$$.ts @@ -31,6 +31,8 @@ struct HomeComponent { Text(this.value1) Text(this.value2) Text(this.value3) + Radio({value: "Radio", group: "1"}) + .checked($$this.value4) } Row() { Button() { @@ -45,6 +47,8 @@ struct HomeComponent { .fontSize(100) .bindPopup($$value6.item1, {message: "This is $$ for Obj"}) Text(this.value3) + Radio({value: "Radio", group: "1"}) + .checked($$value5[0]) } .width(20) } @@ -91,6 +95,8 @@ class HomeComponent extends View { Text.pop(); Text.create(this.value3); Text.pop(); + Radio.create({ value: "Radio", group: "1" }); + Radio.checked(this.value4, newValue => { this.value4 = newValue; }); Row.pop(); Row.create(); Row.width(20); @@ -109,6 +115,8 @@ class HomeComponent extends View { Text.pop(); Text.create(this.value3); Text.pop(); + Radio.create({ value: "Radio", group: "1" }); + Radio.checked(value5[0], newValue => { value5[0] = newValue; }); Row.pop(); Column.pop(); } diff --git a/compiler/webpack.config.js b/compiler/webpack.config.js index 4050b6c..56cdbd9 100644 --- a/compiler/webpack.config.js +++ b/compiler/webpack.config.js @@ -46,27 +46,6 @@ function initConfig(config) { poll: false, ignored: /node_modules/ }, - optimization: { - splitChunks: { - chunks(chunk) { - return !/^\.\/workers\//.test(chunk.name); - }, - minSize: 0, - cacheGroups: { - vendors: { - test: /[\\/]node_modules[\\/]/, - priority: -10, - name: "vendors", - }, - commons: { - name: 'commons', - priority: -20, - minChunks: 2, - reuseExistingChunk: true - } - } - }, - }, output: { path: path.resolve(__dirname, projectConfig.buildPath), filename: '[name].js', @@ -121,7 +100,7 @@ function initConfig(config) { path.join(projectPath, '../../../../../'), './node_modules', path.join(__dirname, 'node_modules'), - path.join(__dirname, '../../api/common') + path.join(__dirname, '../../api') ] }, stats: { preset: 'none' }, @@ -152,6 +131,12 @@ function setProjectConfig(envArgs) { if (envArgs.aceManifestPath) { projectConfig.manifestFilePath = envArgs.aceManifestPath; } + if (envArgs.aceProfilePath) { + projectConfig.aceProfilePath = envArgs.aceProfilePath; + } + if (envArgs.aceModuleJsonPath) { + projectConfig.aceModuleJsonPath = envArgs.aceModuleJsonPath; + } } function setReleaseConfig(config) { @@ -207,11 +192,38 @@ function setCopyPluginConfig(config) { config.plugins.push(new CopyPlugin({ patterns: copyPluginPattrens })); } +function setOptimizationConfig(config) { + if (process.env.compileMode !== 'moduleJson') { + config.optimization = { + splitChunks: { + chunks(chunk) { + return !/^\.\/workers\//.test(chunk.name); + }, + minSize: 0, + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + priority: -10, + name: "vendors", + }, + commons: { + name: 'commons', + priority: -20, + minChunks: 2, + reuseExistingChunk: true + } + } + } + } + } +} + module.exports = (env, argv) => { const config = {}; setProjectConfig(env); loadEntryObj(projectConfig); initConfig(config); + setOptimizationConfig(config); setCopyPluginConfig(config); if (env.isPreview !== "true") { @@ -230,6 +242,7 @@ module.exports = (env, argv) => { } } else { projectConfig.isPreview = true; + projectConfig.checkEntry = env.checkEntry; let port; process.argv.forEach((val, index) => { if(val.startsWith('port=')){