diff --git a/compiler/main.js b/compiler/main.js index 18f2b710d9ffe7fe90081250e44dda7cb58defb6..b356e486be063ca3316d967a42b04b09c7d03675 100644 --- a/compiler/main.js +++ b/compiler/main.js @@ -54,6 +54,7 @@ function initProjectConfig(projectConfig) { function loadEntryObj(projectConfig) { initProjectConfig(projectConfig); setEntryFile(projectConfig); + if(staticPreviewPage) { projectConfig.entryObj['./' + staticPreviewPage] = projectConfig.projectPath + path.sep + staticPreviewPage + '.ets?entry'; diff --git a/compiler/src/compile_info.ts b/compiler/src/compile_info.ts index 80fbe1a464a351a9d5f0844af8ca479fad636e09..f8a1d234d47f8b0d46a2c1f748930995ee96b5c7 100644 --- a/compiler/src/compile_info.ts +++ b/compiler/src/compile_info.ts @@ -28,7 +28,8 @@ import { import { transformLog } from './process_ui_syntax'; import { dollarCollection, - componentCollection + componentCollection, + moduleCollection } from './validate_ui_syntax'; import { decoratorParamSet } from './process_component_member'; import { appComponentCollection } from './process_component_build'; @@ -78,10 +79,12 @@ export class ResultStates { }); if (!projectConfig.isPreview) { - compiler.hooks.compilation.tap('Collect Components', compilation => { - compilation.hooks.additionalAssets.tapAsync('Collect Components', callback => { + 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(",")); + compilation.assets['./module_collection.txt'] = + new RawSource(moduleCollection.size === 0 ? 'NULL' : Array.from(moduleCollection).join(",")); callback(); }); }) diff --git a/compiler/src/component_map.ts b/compiler/src/component_map.ts index e5264eeb1f6528efe2373fe0835a274e4590166f..0306c115e811b61bbfdbe5ce3a4d293da1fe2346 100644 --- a/compiler/src/component_map.ts +++ b/compiler/src/component_map.ts @@ -1,360 +1,424 @@ -/* - * 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. - */ - -export const COMPONENT_MAP: any = { - FormComponent: { - atomic: true, - attrs: [ - 'size', 'moduleName', 'dimension', 'allowUpdate', 'visibility', - 'onAcquired', 'onError', 'onRouter' - ] - }, - Image: { - atomic: true, - attrs: [ - 'alt', 'objectFit', 'matchTextDirection', 'fitOriginalSize', 'objectRepeat', 'renderMode', 'interpolation', - 'onComplete', 'onError', 'onFinish', 'sourceSize', 'fillColor', 'autoResize' - ] - }, - ImageAnimator: { - atomic: true, - attrs: [ - 'images', 'state', 'duration', 'reverse', 'fixedSize', 'preDecode', 'fillMode', 'iterations', 'onStart', - 'onPause', 'onRepeat', 'onCancel', 'onFinish' - ] - }, - Animator: { - atomic: true, - attrs: [ - 'state', 'duration', 'curve', 'delay', 'fillMode', 'iterations', 'playMode', 'motion', 'onStart', - 'onPause', 'onRepeat', 'onCancel', 'onFinish', 'onFrame' - ] - }, - SpringProp: { - atomic: true - }, - SpringMotion: { - atomic: true - }, - FrictionMotion: { - atomic: true - }, - ScrollMotion: { - atomic: true - }, - Text: { - children: ['Span'], - attrs: [ - 'fontColor', 'fontSize', 'fontStyle', 'fontWeight', 'textAlign', 'lineHeight', 'textOverflow', 'maxLines', - 'decoration', 'letterSpacing', 'textCase', 'baselineOffset', 'minFontSize', 'maxFontSize' - ] - }, - TextPicker: { - atomic: true, - attrs: [ - 'defaultPickerItemHeight', 'onAccept', 'onCancel', 'onChange'] - }, - Span: { - atomic: true, - attrs: [ - 'fontColor', 'fontSize', 'fontStyle', 'fontFamily', 'fontWeight', 'decoration', 'letterSpacing', 'textCase' - ] - }, - Button: { - attrs: ['type', 'stateEffect', 'fontColor', 'fontSize', 'fontWeight'] - }, - Divider: { - atomic: true, - attrs: ['color', 'vertical', 'strokeWidth', 'lineCap'] - }, - Piece: { - atomic: true, - attrs: ['iconPosition'] - }, - Slider: { - atomic: true, - attrs: ['blockColor', 'trackColor', 'selectedColor', 'minLabel', 'maxLabel', 'showSteps', 'showTips', 'onChange'] - }, - Counter: { - attrs: [ - 'onStateChange', 'onInc', - 'onDec', 'height', 'width' - ] - }, - Row: { - attrs: ['alignItems'] - }, - Column: { - attrs: ['alignItems'] - }, - Stack: { - attrs: ['alignContent'] - }, - List: { - children: ['ListItem', 'Section'], - attrs: [ - 'listDirection', 'scrollBar', 'edgeEffect', 'divider', 'editMode', 'cachedCount', 'chainAnimation', - 'onScroll', 'onReachStart', 'onReachEnd', 'onScrollStop', 'onItemDelete', 'onItemMove' - ] - }, - ListItem: { - parents: ['List'], - single: true, - attrs: ['sticky', 'editable'] - }, - Grid: { - children: ['GridItem'], - attrs: ['columnsTemplate', 'rowsTemplate', 'columnsGap', 'rowsGap', 'scrollBar', 'scrollBarWidth', 'scrollBarColor'] - }, - GridItem: { - parents: ['Grid'], - single: true, - attrs: ['rowStart', 'rowEnd', 'columnStart', 'columnEnd', 'forceRebuild'] - }, - GridContainer: { - attrs: ['columns', 'sizeType', 'gutter', 'margin'] - }, - Swiper: { - attrs: [ - 'index', 'autoPlay', 'interval', 'indicator', - 'loop', 'duration', 'vertical', 'itemSpace', 'onChange' - ] - }, - Rating: { - attrs: ['stars', 'stepSize', 'starStyle', 'onChange'] - }, - Calendar: { - attrs: [ - 'date', 'showLunar', 'startOfWeek', 'offDays', 'onSelectChange', 'onRequestData', - 'currentData', 'preData', 'nextData', 'needSlide', 'showHoliday', 'direction', - 'currentDayStyle', 'nonCurrentDayStyle', 'todayStyle', 'weekStyle', 'workStateStyle' - ] - }, - Panel: { - attrs: [ - 'type', 'mode', 'dragBar', 'fullHeight', - 'halfHeight', 'miniHeight', 'show', 'onChange' - ] - }, - Navigator: { - single: true, - attrs: ['target', 'type', 'params', 'active'] - }, - Sheet: { - children: ['Section'], - attrs: [] - }, - Section: { - attrs: [] - }, - QRCode: { - attrs: ['color', 'backgroundColor'] - }, - Flex: { - attrs: [] - }, - LoadingProgress: { - atomic: true, - attrs: ['color'] - }, - NavigationView: { - attrs: [] - }, - Scroll: { - attrs: [ - 'scrollable', 'onScroll', 'onScrollEdge', 'onScrollEnd', 'scrollBar', 'scrollBarColor', - 'scrollBarWidth', 'edgeEffect' - ] - }, - Shape: { - children: ['Rect', 'Path', 'Circle', 'Ellipse', 'Shape', 'Polyline', 'Polygon', 'Image', 'Text'], - attrs: [ - 'stroke', 'fill', 'strokeDashOffset', 'strokeLineCap', - 'strokeLineJoin', 'strokeMiterLimit', 'strokeOpacity', - 'fillOpacity', 'strokeWidth', 'antiAlias', 'strokeDashArray', - 'viewPort' - ] - }, - Rect: { - atomic: true, - attrs: [ - 'radiusWidth', 'radiusHeight', 'radius' - ] - }, - Path: { - atomic: true, - attrs: [ - 'commands' - ] - }, - Circle: { - atomic: true - }, - Ellipse: { - atomic: true - }, - Camera: { - atomic: true, - attrs: ['devicePosition'] - }, - Tabs: { - children: ['TabContent'], - attrs: [ - 'initialIndex', 'vertical', 'scrollable', 'barMode', 'barWidth', 'barHeight', 'animationDuration', - 'onChange' - ] - }, - TabContent: { - parents: ['Tabs'], - attrs: ['tabBar'] - }, - PageTransitionEnter: { - atomic: true, - attrs: ['onEnter'] - }, - PageTransitionExit: { - atomic: true, - attrs: ['onExit'] - }, - Blank: { - parents: ['Row', 'Column'], - automic: true, - attrs: ['color'] - }, - RowSplit: { - attrs: ['resizeable'] - }, - ColumnSplit: { - attrs: ['resizeable'] - }, - Toggle: { - attrs: ['onChange', 'selectedColor', 'swithPointStyle'] - }, - AlertDialog: { - attrs: ['show'] - }, - Video: { - atomic: true, - attrs: [ - 'muted', 'autoPlay', 'controls', 'loop', 'objectFit', 'onSeeking', 'onFullscreenChange', - 'onStart', 'onPause', 'onPrepared', 'onFinish', 'onSeeked', 'onUpdate', 'onError' - ] - }, - AbilityComponent: { - attrs: ['onReady', 'onDestroy', 'onAbilityCreated', 'onAbilityMoveToFront', 'onAbilityWillRemove'] - }, - AlphabetIndexer: { - attrs: [ - 'onSelected', 'selectedColor', 'popupColor', 'selectedBackgroundColor', 'popupBackground', 'usingPopup', - 'selectedFont', 'popupFont', 'iteamSize', 'font', 'color', 'alignStyle' - ] - }, - Radio: { - atomic: true, - attrs: ['checked', 'onChange'] - }, - GeometryView: { - atomic: true - }, - DataPanel: { - atomic: false, - attrs: ['closeEffect'] - }, - Badge: { - atomics: true, - attrs: [] - }, - Line: { - atomic: true, - attrs: [ - 'startPoint','endPoint' - ] - }, - Polygon: { - atomic: true, - attrs: ['points'] - }, - Polyline: { - atomic: true, - attrs: ['points'] - }, -}; - -const COMMON_ATTRS: Set = new Set([ - 'width', 'height', 'size', 'constraintSize', 'layoutPriority', 'layoutWeight', - 'padding', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', - 'margin', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom', - 'border', 'borderStyle', 'borderWidth', 'borderColor', 'borderRadius', - 'backgroundColor', 'backgroundImage', 'backgroundImageSize', 'backgroundImagePosition', 'opacity', 'animation', - 'transition', 'navigationTitle', 'navigationSubTitle', 'hideNavigationBar', 'hideNavigationBackButton', - 'toolBar', 'hideToolBar', 'onClick', 'onTouch', 'onKeyEvent', - 'blur', 'backdropBlur', 'windowBlur', 'translate', 'rotate', 'scale', 'transform', - 'onAppear', 'onDisAppear', 'visibility', 'flexBasis', 'flexShrink', 'flexGrow', 'alignSelf', - 'useAlign', 'zIndex', 'sharedTransition', 'direction', 'align', 'position', 'markAnchor', 'offset', - 'enabled', 'aspectRatio', 'displayPriority', 'onDrag', 'onDragEnter', 'onDragMove', 'onDragLeave', 'onDrop', - 'overlay', 'linearGradient', 'sweepGradient', 'radialGradient', 'gridOffset', 'gridSpan', 'useSizeType', - 'motionPath', 'clip', 'shadow', 'mask', 'accessibilityGroup', 'accessibilityText', 'accessibilityDescription', - 'accessibilityImportance', 'onAccessibility' -]); -const TRANSITION_COMMON_ATTRS: Set = new Set([ - 'slide', 'translate', 'scale', 'opacity' -]); -export const GESTURE_ATTRS: Set = new Set([ - 'gesture', 'parallelGesture', 'priorityGesture' -]); - -export const forbiddenUseStateType: Set = new Set(['Scroller', 'SwiperScroller']); - -export const INNER_COMPONENT_NAMES: Set = new Set(); -export const BUILDIN_CONTAINER_COMPONENT: Set = new Set(); -export const BUILDIN_STYLE_NAMES: Set = new Set([ - ...COMMON_ATTRS, ...GESTURE_ATTRS, ...TRANSITION_COMMON_ATTRS -]); -export const AUTOMIC_COMPONENT: Set = new Set(); -export const SINGLE_CHILD_COMPONENT: Set = new Set(); -export const SPECIFIC_CHILD_COMPONENT: Map> = new Map(); -export const GESTURE_TYPE_NAMES: Set = new Set([ - 'TapGesture', 'LongPressGesture', 'PanGesture', 'PinchGesture', 'RotationGesture', 'GestureGroup' -]); -export const CUSTOM_BUILDER_METHOD: Set = new Set(); - -export interface ExtendParamterInterfance { - attribute: string, - parameterCount: number -} -export const EXTEND_ATTRIBUTE: Map> = new Map(); - -(function initComponent() { - Object.keys(COMPONENT_MAP).forEach((componentName) => { - INNER_COMPONENT_NAMES.add(componentName); - if (!COMPONENT_MAP[componentName].atomic) { - BUILDIN_CONTAINER_COMPONENT.add(componentName); - } else { - AUTOMIC_COMPONENT.add(componentName); - } - if (COMPONENT_MAP[componentName].single) { - SINGLE_CHILD_COMPONENT.add(componentName); - } - if (COMPONENT_MAP[componentName].children) { - SPECIFIC_CHILD_COMPONENT.set(componentName, - new Set([...COMPONENT_MAP[componentName].children])); - } - if (COMPONENT_MAP[componentName].attrs && COMPONENT_MAP[componentName].attrs.length) { - COMPONENT_MAP[componentName].attrs.forEach((item) => { - BUILDIN_STYLE_NAMES.add(item); - }); - } - }); -})(); +/* + * 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. + */ + +export const COMPONENT_MAP: any = { + FormComponent: { + atomic: true, + attrs: [ + 'size', 'moduleName', 'dimension', 'allowUpdate', 'visibility', + 'onAcquired', 'onError', 'onRouter' + ] + }, + Image: { + atomic: true, + attrs: [ + 'alt', 'objectFit', 'matchTextDirection', 'fitOriginalSize', 'objectRepeat', 'renderMode', 'interpolation', + 'onComplete', 'onError', 'onFinish', 'sourceSize', 'fillColor', 'autoResize' + ] + }, + ImageAnimator: { + atomic: true, + attrs: [ + 'images', 'state', 'duration', 'reverse', 'fixedSize', 'preDecode', 'fillMode', 'iterations', 'onStart', + 'onPause', 'onRepeat', 'onCancel', 'onFinish' + ] + }, + Animator: { + atomic: true, + attrs: [ + 'state', 'duration', 'curve', 'delay', 'fillMode', 'iterations', 'playMode', 'motion', 'onStart', + 'onPause', 'onRepeat', 'onCancel', 'onFinish', 'onFrame' + ] + }, + Refresh: { + atomic: true, + attrs: [ + 'refreshing', 'offset', 'friction', + 'onStateChange', 'onRefreshing' + ] + }, + SpringProp: { + atomic: true + }, + SpringMotion: { + atomic: true + }, + FrictionMotion: { + atomic: true + }, + ScrollMotion: { + atomic: true + }, + Text: { + children: ['Span'], + attrs: [ + 'fontColor', 'fontSize', 'fontStyle', 'fontWeight', 'textAlign', 'lineHeight', 'textOverflow', 'maxLines', + 'decoration', 'letterSpacing', 'textCase', 'baselineOffset', 'minFontSize', 'maxFontSize' + ] + }, + TextPicker: { + atomic: true, + attrs: ['defaultPickerItemHeight', 'onAccept', 'onCancel', 'onChange'] + }, + DatePicker: { + atomic: true, + attrs: ['lunar', 'onChange', 'useMilitaryTime'] + }, + Span: { + atomic: true, + attrs: [ + 'fontColor', 'fontSize', 'fontStyle', 'fontFamily', 'fontWeight', 'decoration', 'letterSpacing', 'textCase' + ] + }, + Button: { + attrs: ['type', 'stateEffect', 'fontColor', 'fontSize', 'fontWeight'] + }, + Divider: { + atomic: true, + attrs: ['color', 'vertical', 'strokeWidth', 'lineCap'] + }, + Piece: { + atomic: true, + attrs: ['iconPosition'] + }, + Slider: { + atomic: true, + attrs: ['blockColor', 'trackColor', 'selectedColor', 'minLabel','maxLabel', 'showSteps', 'showTips', 'onChange'] + }, + Counter: { + attrs: [ + 'onStateChange', 'onInc', + 'onDec', 'height', 'width' + ] + }, + Row: { + attrs: ['alignItems'] + }, + Column: { + attrs: ['alignItems'] + }, + Stack: { + attrs: ['alignContent'] + }, + List: { + children: ['ListItem', 'Section'], + attrs: [ + 'listDirection', 'scrollBar', 'edgeEffect', 'divider', 'editMode', 'cachedCount', 'chainAnimation', + 'onScroll', 'onReachStart', 'onReachEnd', 'onScrollStop', 'onItemDelete', 'onItemMove' + ] + }, + ListItem: { + parents: ['List'], + single: true, + attrs: ['sticky', 'editable'] + }, + Grid: { + children: ['GridItem'], + attrs: ['columnsTemplate', 'rowsTemplate', 'columnsGap', 'rowsGap', 'scrollBar', 'scrollBarWidth', 'scrollBarColor'] + }, + GridItem: { + parents: ['Grid'], + single: true, + attrs: ['rowStart', 'rowEnd', 'columnStart', 'columnEnd', 'forceRebuild'] + }, + GridContainer: { + attrs: ['columns', 'sizeType', 'gutter', 'margin'] + }, + Hyperlink: { + attrs: ['color'] + }, + Swiper: { + attrs: [ + 'index', 'autoPlay', 'interval', 'indicator', + 'loop', 'duration', 'vertical', 'itemSpace', 'displayMode', 'onChange' + ] + }, + Rating: { + attrs: ['stars', 'stepSize', 'starStyle', 'onChange'] + }, + Calendar: { + attrs: [ + 'date', 'showLunar', 'startOfWeek', 'offDays', 'onSelectChange', 'onRequestData', + 'currentData', 'preData', 'nextData', 'needSlide', 'showHoliday', 'direction', + 'currentDayStyle', 'nonCurrentDayStyle', 'todayStyle', 'weekStyle', 'workStateStyle' + ] + }, + Panel: { + attrs: [ + 'type', 'mode', 'dragBar', 'fullHeight', + 'halfHeight', 'miniHeight', 'show', 'onChange' + ] + }, + Navigator: { + single: true, + attrs: ['target', 'type', 'params', 'active'] + }, + Sheet: { + children: ['Section'], + attrs: [] + }, + Section: { + attrs: [] + }, + QRCode: { + attrs: ['color', 'backgroundColor'] + }, + Flex: { + attrs: [] + }, + LoadingProgress: { + atomic: true, + attrs: ['color'] + }, + NavigationView: { + attrs: [] + }, + Scroll: { + attrs: [ + 'scrollable', 'onScroll', 'onScrollEdge', 'onScrollEnd', 'scrollBar', 'scrollBarColor', + 'scrollBarWidth', 'edgeEffect' + ] + }, + Shape: { + children: ['Rect', 'Path', 'Circle', 'Ellipse', 'Shape', 'Polyline', 'Polygon', 'Image', 'Text'], + attrs: [ + 'stroke', 'fill', 'strokeDashOffset', 'strokeLineCap', + 'strokeLineJoin', 'strokeMiterLimit', 'strokeOpacity', + 'fillOpacity', 'strokeWidth', 'antiAlias', 'strokeDashArray', + 'viewPort' + ] + }, + Progress: { + atomic: true, + attrs: [ + 'value', 'color', 'cricularStyle' + ] + }, + Rect: { + atomic: true, + attrs: [ + 'radiusWidth', 'radiusHeight', 'radius' + ] + }, + Path: { + atomic: true, + attrs: [ + 'commands' + ] + }, + Circle: { + atomic: true + }, + Ellipse: { + atomic: true + }, + Camera: { + atomic: true, + attrs: ['devicePosition'] + }, + Tabs: { + children: ['TabContent'], + attrs: [ + 'vertical', 'scrollable', 'barMode', 'barWidth', 'barHeight', 'animationDuration', + 'onChange' + ] + }, + TabContent: { + parents: ['Tabs'], + attrs: ['tabBar'] + }, + PageTransitionEnter: { + atomic: true, + attrs: ['onEnter'] + }, + PageTransitionExit: { + atomic: true, + attrs: ['onExit'] + }, + Blank: { + parents: ['Row', 'Column'], + automic: true, + attrs: ['color'] + }, + RowSplit: { + attrs: ['resizeable'] + }, + ColumnSplit: { + attrs: ['resizeable'] + }, + Toggle: { + attrs: ['onChange', 'selectedColor', 'swithPointStyle'] + }, + AlertDialog: { + attrs: ['show'] + }, + ActionSheet: { + attrs: ['show'] + }, + Video: { + atomic: true, + attrs: [ + 'muted', 'autoPlay', 'controls', 'loop', 'objectFit', 'onSeeking', 'onFullscreenChange', + 'onStart', 'onPause', 'onPrepared', 'onFinish', 'onSeeked', 'onUpdate', 'onError' + ] + }, + AbilityComponent: { + attrs: ['onReady', 'onDestroy', 'onAbilityCreated', 'onAbilityMoveToFront', 'onAbilityWillRemove'] + }, + AlphabetIndexer: { + attrs: [ + 'onSelected', 'selectedColor', 'popupColor', 'selectedBackgroundColor', 'popupBackground', 'usingPopup', + 'selectedFont', 'popupFont', 'itemSize', 'font', 'color', 'alignStyle' + ] + }, + Radio: { + atomic: true, + attrs: ['checked', 'onChange'] + }, + GeometryView: { + atomic: true + }, + DataPanel: { + atomic: false, + attrs: ['closeEffect'] + }, + Badge: { + atomics: true, + attrs: [] + }, + Line: { + atomic: true, + attrs: [ + 'startPoint','endPoint' + ] + }, + Polygon: { + atomic: true, + attrs: ['points'] + }, + Polyline: { + atomic: true, + attrs: ['points'] + }, + Gauge: { + atomic: true, + attrs: ['value', 'startAngle', 'endAngle', 'colors', 'strokeWidth', 'labelTextConfig', 'labelColorConfig'] + }, + TextArea: { + atomic: true, + attrs: [ + 'placeholderColor', 'placeholderFont', 'textAlign', 'caretColor', 'correction', 'onChange' + ] + }, + TextInput: { + atomic: true, + attrs: [ + 'textInputType', 'placeholderColor', 'placeholderFont', 'textInputAction', 'inputFilter', 'caretColor', + 'correction', 'onEditChanged', 'onSubmit', 'onChange' + ] + }, + Marquee: { + atomic: true, + attrs: ['onStart', 'onBounce', 'onFinish'] + }, + Menu: { + children: ['Option'], + attrs: ['show', 'showPosition'], + }, + Option: { + parents: ['Menu'], + attrs: [], + }, +}; + +const COMMON_ATTRS: Set = new Set([ + 'width', 'height', 'size', 'constraintSize', 'layoutPriority', 'layoutWeight', + 'padding', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', + 'margin', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom', + 'border', 'borderStyle', 'borderWidth', 'borderColor', 'borderRadius', + 'backgroundColor', 'backgroundImage', 'backgroundImageSize', 'backgroundImagePosition', + 'opacity', 'animation', 'transition', + 'navigationTitle', 'navigationSubTitle', 'hideNavigationBar', 'hideNavigationBackButton', + 'toolBar', 'hideToolBar', 'onClick', 'onTouch', 'onKeyEvent', 'onHover', + 'blur', 'backdropBlur', 'windowBlur', 'translate', 'rotate', 'scale', 'transform', + 'onAppear', 'onDisAppear', 'visibility', 'flexBasis', 'flexShrink', 'flexGrow', 'alignSelf', + 'useAlign', 'zIndex', 'sharedTransition', 'direction', 'align', 'position', 'markAnchor', 'offset', + 'enabled', 'aspectRatio', 'displayPriority', + 'onDrag', 'onDragEnter', 'onDragMove', 'onDragLeave', 'onDrop', + 'overlay', 'linearGradient', 'sweepGradient', 'radialGradient', + 'gridOffset', 'gridSpan', 'useSizeType', + 'motionPath', 'clip', 'shadow', 'mask', + 'accessibilityGroup', 'accessibilityText', 'accessibilityDescription', + 'accessibilityImportance', 'onAccessibility', 'grayscale', 'brightness', 'contrast', + 'saturate', 'geometryTransition', + 'bindPopup', 'colorBlend', 'invert', 'sepia', 'hueRotate' +]); +const TRANSITION_COMMON_ATTRS: Set = new Set([ + 'slide', 'translate', 'scale', 'opacity' +]); +export const GESTURE_ATTRS: Set = new Set([ + 'gesture', 'parallelGesture', 'priorityGesture' +]); + +export const forbiddenUseStateType: Set = new Set(['Scroller', 'SwiperScroller']); + +export const INNER_COMPONENT_NAMES: Set = new Set(); +export const BUILDIN_CONTAINER_COMPONENT: Set = new Set(); +export const BUILDIN_STYLE_NAMES: Set = new Set([ + ...COMMON_ATTRS, ...GESTURE_ATTRS, ...TRANSITION_COMMON_ATTRS +]); +export const AUTOMIC_COMPONENT: Set = new Set(); +export const SINGLE_CHILD_COMPONENT: Set = new Set(); +export const SPECIFIC_CHILD_COMPONENT: Map> = new Map(); +export const GESTURE_TYPE_NAMES: Set = new Set([ + 'TapGesture', 'LongPressGesture', 'PanGesture', 'PinchGesture', 'RotationGesture', 'GestureGroup' +]); +export const CUSTOM_BUILDER_METHOD: Set = new Set(); + +export interface ExtendParamterInterfance { + attribute: string, + parameterCount: number +} +export const EXTEND_ATTRIBUTE: Map> = new Map(); + +export const JS_BIND_COMPONENTS: Set = new Set([ + ...GESTURE_TYPE_NAMES, 'Gesture', + 'PanGestureOption', 'CustomDialogController', 'Storage', 'Scroller', 'SwiperController', + 'TabsController', 'CalendarController', 'AbilityController', 'VideoController' +]); + +(function initComponent() { + Object.keys(COMPONENT_MAP).forEach((componentName) => { + INNER_COMPONENT_NAMES.add(componentName); + JS_BIND_COMPONENTS.add(componentName); + if (!COMPONENT_MAP[componentName].atomic) { + BUILDIN_CONTAINER_COMPONENT.add(componentName); + } else { + AUTOMIC_COMPONENT.add(componentName); + } + if (COMPONENT_MAP[componentName].single) { + SINGLE_CHILD_COMPONENT.add(componentName); + } + if (COMPONENT_MAP[componentName].children) { + SPECIFIC_CHILD_COMPONENT.set(componentName, + new Set([...COMPONENT_MAP[componentName].children])); + } + if (COMPONENT_MAP[componentName].attrs && COMPONENT_MAP[componentName].attrs.length) { + COMPONENT_MAP[componentName].attrs.forEach((item) => { + BUILDIN_STYLE_NAMES.add(item); + }); + } + }); +})(); diff --git a/compiler/src/create.ts b/compiler/src/create.ts index c7e0c9cb939aabdd5485cd633a327e979bb9fcd6..f24f1443eaa757e77a69a90fce5ba052606c9736 100644 --- a/compiler/src/create.ts +++ b/compiler/src/create.ts @@ -1,109 +1,109 @@ -/* - * 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. - */ - -const fs = require('fs'); -const program = require('commander'); - -program.parse(process.argv); -let name:string = 'HelloAce'; -let appID:string = 'ace.helloworld'; -let appName:string = 'HelloAce'; -if (program.args && program.args[0]) { - name = program.args[0]; - appID = program.args[0]; - appName = program.args[0]; -} - -const regPath: RegExp = /[`~!@#$%^&*()_+<>?:"{},./;'[\]]/im; - -/* - * Create sample project and files. - * @param dist {String} - */ -function createProject(dist: string) { - const dist_ = dist.trim().split('/'); - if (dist_.length > 1 || regPath.test(dist)) { - return console.error( - 'ERROR: The project name cannot be a path nor contain any special symbol.\n' + - "NOTE: To create the template project, run 'npm run create' in the root directory.\n" + - "NOTE: To customize the project name, run 'npm run create '."); - } - const appPath:string = dist + '/app.ets'; - const manifestPath:string = dist + '/manifest.json'; - const indexPath:string = dist + '/pages/index.ets'; - - const app:string = `export default { - onCreate() { - console.info('Application onCreate') - }, - onDestroy() { - console.info('Application onDestroy') - }, -}`; - - const manifest:string = `{ - "appID": "com.huawei.` + appID + `", - "appName": "` + appName + `", - "versionName": "1.0.0", - "versionCode": 1, - "minPlatformVersion": "1.0.1", - "pages": [ - "pages/index" - ], - "window": { - "designWidth": 750, - "autoDesignWidth": false - } -}`; - - const index:string = `@Entry -@Component -struct MyComponent { - private value1: string = "hello world 1"; - private value2: string = "hello world 2"; - private value3: string = "hello world 3"; - - build() { - Column() { - Text(this.value1); - Text(this.value2); - Text(this.value3); - } - } -}`; - - fs.mkdir(dist + '/pages', { recursive: true }, (err) => { - if (err) { - return console.error('ERROR: Failed to create project directory.'); - } - fs.writeFile(appPath, app, (err) => { - if (err) { - return console.error('ERROR: Failed to write app.ets.'); - } - }); - fs.writeFile(manifestPath, manifest, (err) => { - if (err) { - return console.error('ERROR: Failed to write manifest.json.'); - } - }); - fs.writeFile(indexPath, index, (err) => { - if (err) { - return console.error('ERROR: Failed to write index.ets.'); - } - }); - }); -} - -createProject(name); +/* + * 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. + */ + +const fs = require('fs'); +const program = require('commander'); + +program.parse(process.argv); +let name:string = 'HelloAce'; +let appID:string = 'ace.helloworld'; +let appName:string = 'HelloAce'; +if (program.args && program.args[0]) { + name = program.args[0]; + appID = program.args[0]; + appName = program.args[0]; +} + +const regPath: RegExp = /[`~!@#$%^&*()_+<>?:"{},./;'[\]]/im; + +/* + * Create sample project and files. + * @param dist {String} + */ +function createProject(dist: string) { + const dist_ = dist.trim().split('/'); + if (dist_.length > 1 || regPath.test(dist)) { + return console.error( + 'ERROR: The project name cannot be a path nor contain any special symbol.\n' + + "NOTE: To create the template project, run 'npm run create' in the root directory.\n" + + "NOTE: To customize the project name, run 'npm run create '."); + } + const appPath:string = dist + '/app.ets'; + const manifestPath:string = dist + '/manifest.json'; + const indexPath:string = dist + '/pages/index.ets'; + + const app:string = `export default { + onCreate() { + console.info('Application onCreate') + }, + onDestroy() { + console.info('Application onDestroy') + }, +}`; + + const manifest:string = `{ + "appID": "com.huawei.` + appID + `", + "appName": "` + appName + `", + "versionName": "1.0.0", + "versionCode": 1, + "minPlatformVersion": "1.0.1", + "pages": [ + "pages/index" + ], + "window": { + "designWidth": 750, + "autoDesignWidth": false + } +}`; + + const index:string = `@Entry +@Component +struct MyComponent { + private value1: string = "hello world 1"; + private value2: string = "hello world 2"; + private value3: string = "hello world 3"; + + build() { + Column() { + Text(this.value1); + Text(this.value2); + Text(this.value3); + } + } +}`; + + fs.mkdir(dist + '/pages', { recursive: true }, (err) => { + if (err) { + return console.error('ERROR: Failed to create project directory.'); + } + fs.writeFile(appPath, app, (err) => { + if (err) { + return console.error('ERROR: Failed to write app.ets.'); + } + }); + fs.writeFile(manifestPath, manifest, (err) => { + if (err) { + return console.error('ERROR: Failed to write manifest.json.'); + } + }); + fs.writeFile(indexPath, index, (err) => { + if (err) { + return console.error('ERROR: Failed to write index.ets.'); + } + }); + }); +} + +createProject(name); diff --git a/compiler/src/pre_define.ts b/compiler/src/pre_define.ts index fe9eb240892681d1b460c83adc379334731ce558..ce665c478406fc5ebb10fac4f196ef08d36fe897 100644 --- a/compiler/src/pre_define.ts +++ b/compiler/src/pre_define.ts @@ -13,7 +13,8 @@ * limitations under the License. */ -export const NATIVE_MODULE: Set = new Set(['app', 'router', 'curves', 'matrix4']); +export const NATIVE_MODULE: Set = new Set( + ['system.app', 'ohos.app', 'system.router', 'system.curves', 'ohos.curves', 'system.matrix4', 'ohos.matrix4']); export const SYSTEM_PLUGIN: string = 'system'; export const OHOS_PLUGIN: string = 'ohos'; @@ -62,6 +63,11 @@ export const APP_STORAGE_GET_OR_SET: string = 'GetOrCreate'; export const PAGE_ENTRY_FUNCTION_NAME: string = 'loadDocument'; export const COMPONENT_DECORATOR_NAME_COMPONENT: string = 'Component'; +export const COMPONENT_DECORATOR_NAME_CUSTOMDIALOG: string = 'CustomDialog'; +export const CUSTOM_DECORATOR_NAME: Set = new Set([ + COMPONENT_DECORATOR_NAME_COMPONENT, COMPONENT_DECORATOR_NAME_CUSTOMDIALOG +]); + export const EXTNAME_ETS: string = '.ets'; export const NODE_MODULES: string = 'node_modules'; export const INDEX_ETS: string = 'index.ets'; @@ -137,12 +143,22 @@ export const GESTURE_ENUM_VALUE_LOW: string = 'Low'; export const GESTURE_ENUM_VALUE_PARALLEL: string = 'Parallel'; export const RESOURCE: string = '$r'; +export const RESOURCE_RAWFILE: string = '$rawfile'; +export const RESOURCE_NAME_ID: string = 'id'; +export const RESOURCE_NAME_TYPE: string = 'type'; +export const RESOURCE_NAME_PARAMS: string = 'params'; export const RESOURCE_TYPE = { color: 10001, float: 10002, string: 10003, plural: 10004, - media: 20000 + boolean: 10005, + intarray: 10006, + integer: 10007, + pattern: 10008, + strarray: 10009, + media: 20000, + rawfile: 30000 }; export const WORKERS_DIR: string = 'workers'; diff --git a/compiler/src/pre_process.ts b/compiler/src/pre_process.ts index ce86a4c2a7de72666792da060b0f02919a4bad18..752cd1242f6f4bb1e02c8ae4dcfa54a684deac07 100644 --- a/compiler/src/pre_process.ts +++ b/compiler/src/pre_process.ts @@ -1,38 +1,42 @@ -/* - * 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 { - sourceReplace, - validateUISyntax -} from './validate_ui_syntax'; -import { - LogInfo, - emitLogInfo -} from './utils'; -import { BUILD_ON } from './pre_define'; - -function preProcess(source: string): string { - process.env.compiler = BUILD_ON; - const newContent: string = sourceReplace(source); - const log: LogInfo[] = - validateUISyntax(source, newContent, this.resourcePath, this.resourceQuery); - if (log.length) { - emitLogInfo(this, log); - } - - return newContent; -} - -module.exports = preProcess; +/* + * 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 { + sourceReplace, + validateUISyntax, + processSystemApi +} from './validate_ui_syntax'; +import { + LogInfo, + emitLogInfo +} from './utils'; +import { BUILD_ON } from './pre_define'; + +function preProcess(source: string): string { + process.env.compiler = BUILD_ON; + if (/\.ets$/.test(this.resourcePath)) { + const newContent: string = sourceReplace(source); + const log: LogInfo[] = + validateUISyntax(source, newContent, this.resourcePath, this.resourceQuery); + if (log.length) { + emitLogInfo(this, log); + } + return newContent; + } else { + return processSystemApi(source); + } +} + +module.exports = preProcess; diff --git a/compiler/src/process_ability_entry_file.ts b/compiler/src/process_ability_entry_file.ts deleted file mode 100644 index 00b67886ac60a30a2f8e250c4e94d3560a8edcc5..0000000000000000000000000000000000000000 --- a/compiler/src/process_ability_entry_file.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 path from 'path'; - -import { abilityConfig } from '../main'; - -module.exports = function processAbilityEntryFile(source: string): string { - if (path.basename(this.resourcePath) === abilityConfig.abilityEntryFile) { - source = source.replace(/exports\.default/, 'globalThis.exports.default'); - } - return source; -} \ No newline at end of file diff --git a/compiler/src/process_component_build.ts b/compiler/src/process_component_build.ts index c8a37eb4c29199d3ce55b859ab3700aea92b62e8..fad16c3b04e57abb68d14598206da58a4aaf2692 100644 --- a/compiler/src/process_component_build.ts +++ b/compiler/src/process_component_build.ts @@ -1,690 +1,690 @@ -/* - * 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 { - COMPONENT_RENDER_FUNCTION, - COMPONENT_CREATE_FUNCTION, - COMPONENT_POP_FUNCTION, - COMPONENT_BUTTON, - COMPONENT_BLANK, - COMPONENT_CREATE_LABEL_FUNCTION, - COMPONENT_CREATE_CHILD_FUNCTION, - COMPONENT_FOREACH, - COMPONENT_LAZYFOREACH, - IS_RENDERING_IN_PROGRESS, - FOREACH_OBSERVED_OBJECT, - FOREACH_GET_RAW_OBJECT, - COMPONENT_IF, - COMPONENT_IF_BRANCH_ID_FUNCTION, - COMPONENT_IF_UNDEFINED, - ATTRIBUTE_ANIMATION, - GLOBAL_CONTEXT, - COMPONENT_GESTURE, - COMPONENT_GESTURE_GROUP, - GESTURE_ATTRIBUTE, - PARALLEL_GESTURE_ATTRIBUTE, - PRIORITY_GESTURE_ATTRIBUTE, - GESTURE_ENUM_KEY, - GESTURE_ENUM_VALUE_HIGH, - GESTURE_ENUM_VALUE_LOW, - GESTURE_ENUM_VALUE_PARALLEL, - COMPONENT_TRANSITION_NAME, - COMPONENT_DEBUGLINE_FUNCTION -} from './pre_define'; -import { - INNER_COMPONENT_NAMES, - BUILDIN_CONTAINER_COMPONENT, - BUILDIN_STYLE_NAMES, - CUSTOM_BUILDER_METHOD, - GESTURE_ATTRS, - GESTURE_TYPE_NAMES, - EXTEND_ATTRIBUTE -} from './component_map'; -import { componentCollection } from './validate_ui_syntax'; -import { processCustomComponent } from './process_custom_component'; -import { - LogType, - LogInfo, - componentInfo, - createFunction -} from './utils'; -import { projectConfig } from '../main'; -import { transformLog } from './process_ui_syntax'; - -export const appComponentCollection: Set = new Set(); - -export function processComponentBuild(node: ts.MethodDeclaration, - log: LogInfo[]): ts.MethodDeclaration { - let newNode: ts.MethodDeclaration; - const renderNode: ts.Identifier = ts.factory.createIdentifier(COMPONENT_RENDER_FUNCTION); - if (node.body && node.body.statements && node.body.statements.length && - validateRootNode(node, log)) { - newNode = ts.factory.updateMethodDeclaration(node, node.decorators, node.modifiers, - node.asteriskToken, renderNode, node.questionToken, node.typeParameters, node.parameters, - node.type, processComponentBlock(node.body, false, log)); - } else { - newNode = ts.factory.updateMethodDeclaration(node, node.decorators, node.modifiers, - node.asteriskToken, renderNode, node.questionToken, node.typeParameters, node.parameters, - node.type, node.body); - } - return newNode; -} - -export function processComponentBlock(node: ts.Block, isLazy: boolean, log: LogInfo[], - isTransition: boolean = false): ts.Block { - const newStatements: ts.Statement[] = []; - processComponentChild(node, newStatements, log); - if (isLazy) { - newStatements.unshift(createRenderingInProgress(true)); - } - if (isTransition) { - newStatements.unshift(ts.factory.createExpressionStatement( - createFunction(ts.factory.createIdentifier(COMPONENT_TRANSITION_NAME), - ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))); - newStatements.push(ts.factory.createExpressionStatement( - createFunction(ts.factory.createIdentifier(COMPONENT_TRANSITION_NAME), - ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); - } - if (isLazy) { - newStatements.push(createRenderingInProgress(false)); - } - return ts.factory.updateBlock(node, newStatements); -} - -function validateRootNode(node: ts.MethodDeclaration, log: LogInfo[]): boolean { - let isValid: boolean = false; - if (node.body.statements.length < 4) { - switch (node.body.statements.length) { - case 1: - if (validateFirstNode(node.body.statements[0])) { - isValid = true; - } - break; - case 2: - if (validateFirstNode(node.body.statements[0]) && - validateBlockNode(node.body.statements[1])) { - isValid = true; - } - break; - case 3: - if (validateFirstNode(node.body.statements[0]) && - validateBlockNode(node.body.statements[1]) && - validateSecondNode(node.body.statements[2])) { - isValid = true; - } - break; - } - } - if (!isValid) { - log.push({ - type: LogType.ERROR, - message: `There should have a root container component.`, - pos: node.body.statements.pos - }); - } - return isValid; -} - -function processComponentChild(node: ts.Block, newStatements: ts.Statement[], - log: LogInfo[]): void { - if (node.statements.length) { - node.statements.forEach((item, index) => { - if (ts.isExpressionStatement(item)) { - switch (getComponentType(item, log)) { - case ComponentType.innerComponent: - processInnerComponent(item, index, Array.from(node.statements), newStatements, log); - break; - case ComponentType.customComponent: - processCustomComponent(item, newStatements, log); - break; - case ComponentType.forEachComponent: - processForEachComponent(item, newStatements, log); - break; - case ComponentType.customBuilderMethod: - newStatements.push(item); - break; - } - } else if (ts.isIfStatement(item)) { - appComponentCollection.add(COMPONENT_IF); - processIfStatement(item, newStatements, log); - } else if (!ts.isBlock(item)) { - log.push({ - type: LogType.ERROR, - message: `Only UI component syntax can be written in build method.`, - pos: item.getStart() - }); - } - }); - } -} - -function processInnerComponent(node: ts.ExpressionStatement, index: number, arr: ts.Statement[], - newStatements: ts.Statement[], log: LogInfo[]): void { - const res: CreateResult = createComponent(node, COMPONENT_CREATE_FUNCTION); - newStatements.push(res.newNode); - if (projectConfig.isPreview) { - const posOfNode: ts.LineAndCharacter = - transformLog.sourceFile.getLineAndCharacterOfPosition(node.getStart()); - const projectPath: string = projectConfig.projectPath; - const curFileName: string = transformLog.sourceFile.fileName.replace(/.ts$/, ''); - const debugInfo: string = - `${path.relative(projectPath, curFileName).replace(/\\+/g, '/')}` + - `(${posOfNode.line + 1}:${posOfNode.character + 1})`; - const debugNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( - createFunction(ts.factory.createIdentifier(getName(node)), - ts.factory.createIdentifier(COMPONENT_DEBUGLINE_FUNCTION), - ts.factory.createNodeArray([ts.factory.createStringLiteral(debugInfo)]))); - newStatements.push(debugNode); - } - if (index + 1 < arr.length && ts.isBlock(arr[index + 1])) { - if (res.isButton) { - if (projectConfig.isPreview) { - newStatements.splice(-2, 1, createComponent(node, COMPONENT_CREATE_CHILD_FUNCTION).newNode); - } else { - newStatements.splice(-1, 1, createComponent(node, COMPONENT_CREATE_CHILD_FUNCTION).newNode); - } - } - if (index + 2 < arr.length && ts.isExpressionStatement(arr[index + 2]) && - isAttributeNode(arr[index + 2] as ts.ExpressionStatement)) { - bindComponentAttr(arr[index + 2] as ts.ExpressionStatement, res.identifierNode, newStatements, log); - } - processComponentChild(arr[index + 1] as ts.Block, newStatements, log); - } else { - bindComponentAttr(node, res.identifierNode, newStatements, log); - } - if (res.isContainerComponent || res.needPop) { - newStatements.push(createComponent(node, COMPONENT_POP_FUNCTION).newNode); - } -} - -function processForEachComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], - log: LogInfo[]): void { - const popNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(createFunction( - // @ts-ignore - node.expression.expression as ts.Identifier, - ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)); - if (ts.isCallExpression(node.expression)) { - const propertyNode: ts.PropertyAccessExpression = ts.factory.createPropertyAccessExpression( - node.expression.expression as ts.Identifier, - ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION) - ); - const argumentsArray: ts.Expression[] = Array.from(node.expression.arguments); - let arrayObserveredObject: ts.CallExpression; - if (argumentsArray.length) { - arrayObserveredObject = ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(FOREACH_OBSERVED_OBJECT), - ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [argumentsArray[0]]); - } - argumentsArray.splice(0, 1, arrayObserveredObject); - const newArrowNode: ts.ArrowFunction = processForEachBlock(node.expression, log); - if (newArrowNode) { - argumentsArray.splice(1, 1, newArrowNode); - } - node = addForEachId(ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression( - node.expression, propertyNode, node.expression.typeArguments, argumentsArray))); - } - newStatements.push(node, popNode); -} - -function addForEachId(node: ts.ExpressionStatement): ts.ExpressionStatement { - const forEachComponent: ts.CallExpression = node.expression as ts.CallExpression; - return ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression( - forEachComponent, forEachComponent.expression, forEachComponent.typeArguments, - [ts.factory.createStringLiteral((++componentInfo.id).toString()), ts.factory.createThis(), - ...forEachComponent.arguments])); -} - -function processForEachBlock(node: ts.CallExpression, log: LogInfo[]): ts.ArrowFunction { - if (node.arguments.length > 1 && ts.isArrowFunction(node.arguments[1])) { - const isLazy: boolean = node.expression.getText() === COMPONENT_LAZYFOREACH; - const arrowNode: ts.ArrowFunction = node.arguments[1] as ts.ArrowFunction; - const body: ts.ConciseBody = arrowNode.body; - if (node.arguments.length > 2 && !ts.isArrowFunction(node.arguments[2])) { - log.push({ - type: LogType.ERROR, - message: 'There should be wrapped in curly braces in ForEach.', - pos: body.getStart() - }); - } else if (!ts.isBlock(body)) { - const statement: ts.Statement = ts.factory.createExpressionStatement(body); - const blockNode: ts.Block = ts.factory.createBlock([statement], true); - // @ts-ignore - statement.parent = blockNode; - return ts.factory.updateArrowFunction( - arrowNode, arrowNode.modifiers, arrowNode.typeParameters, arrowNode.parameters, - arrowNode.type, arrowNode.equalsGreaterThanToken, processComponentBlock(blockNode, isLazy, log)); - } else { - return ts.factory.updateArrowFunction( - arrowNode, arrowNode.modifiers, arrowNode.typeParameters, arrowNode.parameters, - arrowNode.type, arrowNode.equalsGreaterThanToken, processComponentBlock(body, isLazy, log)); - } - } - return null; -} - -function createRenderingInProgress(isTrue: boolean): ts.ExpressionStatement { - return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createThis(), - ts.factory.createIdentifier(IS_RENDERING_IN_PROGRESS) - ), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - isTrue ? ts.factory.createTrue() : ts.factory.createFalse() - )); -} - -function processIfStatement(node: ts.IfStatement, newStatements: ts.Statement[], - log: LogInfo[]): void { - const ifCreate: ts.ExpressionStatement = createIfCreate(); - const newIfNode: ts.IfStatement = processInnerIfStatement(node, 0, log); - const ifPop: ts.ExpressionStatement = createIfPop(); - newStatements.push(ifCreate, newIfNode, ifPop); -} - -function processInnerIfStatement(node: ts.IfStatement, id: number, log: LogInfo[]): ts.IfStatement { - if (ts.isIdentifier(node.expression) && node.expression.originalKeywordKind === undefined && - !node.expression.escapedText) { - log.push({ - type: LogType.ERROR, - message: 'Condition expression cannot be null in if statement.', - pos: node.expression.getStart() - }); - node = ts.factory.updateIfStatement(node, ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED), - node.thenStatement, node.elseStatement); - } - const newThenStatement: ts.Statement = processThenStatement(node.thenStatement, id, log); - const newElseStatement: ts.Statement = processElseStatement(node.elseStatement, id, log); - const newIfNode: ts.IfStatement = ts.factory.updateIfStatement( - node, node.expression, newThenStatement, newElseStatement); - return newIfNode; -} - -function processThenStatement(thenStatement: ts.Statement, id: number, - log: LogInfo[]): ts.Statement { - if (ts.isExpressionStatement(thenStatement) && ts.isIdentifier(thenStatement.expression) && - thenStatement.expression.originalKeywordKind === undefined && - !thenStatement.expression.escapedText) { - log.push({ - type: LogType.ERROR, - message: 'Then statement cannot be null in if statement.', - pos: thenStatement.expression.getStart() - }); - } - if (thenStatement) { - if (ts.isBlock(thenStatement)) { - thenStatement = processIfBlock(thenStatement, id, log); - } else if (ts.isIfStatement(thenStatement)) { - thenStatement = processInnerIfStatement(thenStatement, 0, log); - thenStatement = ts.factory.createBlock( - [createIfCreate(), createIfBranchId(id), thenStatement, createIfPop()], true); - } else { - thenStatement = ts.factory.createBlock([thenStatement], true); - thenStatement = processIfBlock(thenStatement as ts.Block, id, log); - } - } - return thenStatement; -} - -function processElseStatement(elseStatement: ts.Statement, id: number, - log: LogInfo[]): ts.Statement { - if (elseStatement) { - if (ts.isBlock(elseStatement)) { - elseStatement = processIfBlock(elseStatement, id + 1, log); - } else if (ts.isIfStatement(elseStatement)) { - elseStatement = processInnerIfStatement(elseStatement, id + 1, log); - } else { - elseStatement = ts.factory.createBlock([elseStatement], true); - elseStatement = processIfBlock(elseStatement as ts.Block, id + 1, log); - } - } - return elseStatement; -} - -function processIfBlock(block: ts.Block, id: number, log: LogInfo[]): ts.Block { - return addIfBranchId(id, processComponentBlock(block, false, log)); -} - -function addIfBranchId(id: number, container: ts.Block): ts.Block { - return ts.factory.updateBlock(container, [createIfBranchId(id), ...container.statements]); -} - -function createIf(): ts.Identifier { - return ts.factory.createIdentifier(COMPONENT_IF); -} - -function createIfCreate(): ts.ExpressionStatement { - return ts.factory.createExpressionStatement(createFunction(createIf(), - ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), ts.factory.createNodeArray([]))); -} - -function createIfPop(): ts.ExpressionStatement { - return ts.factory.createExpressionStatement(createFunction(createIf(), - ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)); -} - -function createIfBranchId(id: number): ts.ExpressionStatement { - return ts.factory.createExpressionStatement(createFunction(createIf(), - ts.factory.createIdentifier(COMPONENT_IF_BRANCH_ID_FUNCTION), - ts.factory.createNodeArray([ts.factory.createNumericLiteral(id)]))); -} - -interface CreateResult { - newNode: ts.ExpressionStatement; - identifierNode: ts.Identifier; - isContainerComponent: boolean; - isButton: boolean; - needPop: boolean; -} - -function createComponent(node: ts.ExpressionStatement, type: string): CreateResult { - const res: CreateResult = { - newNode: node, - identifierNode: null, - isContainerComponent: false, - isButton: false, - needPop: false - }; - let identifierNode: ts.Identifier = ts.factory.createIdentifier(type); - let temp: any = node.expression; - while (temp && !ts.isIdentifier(temp) && temp.expression) { - temp = temp.expression; - } - if (temp && temp.parent && ts.isCallExpression(temp.parent) && ts.isIdentifier(temp)) { - if (temp.getText() === COMPONENT_BUTTON && type !== COMPONENT_POP_FUNCTION) { - res.isButton = true; - identifierNode = type === COMPONENT_CREATE_CHILD_FUNCTION - ? ts.factory.createIdentifier(COMPONENT_CREATE_CHILD_FUNCTION) - : ts.factory.createIdentifier(COMPONENT_CREATE_LABEL_FUNCTION); - } - if (temp.getText() === COMPONENT_BLANK) { - res.needPop = true; - } - if (BUILDIN_CONTAINER_COMPONENT.has(temp.getText())) { - res.isContainerComponent = true; - } - res.newNode = type === COMPONENT_POP_FUNCTION - ? ts.factory.updateExpressionStatement(node, - createFunction(temp, identifierNode, null)) - : ts.factory.updateExpressionStatement(node, - createFunction(temp, identifierNode, temp.parent.arguments)); - res.identifierNode = temp; - } - return res; -} - -interface AnimationInfo { - statement: ts.Statement, - kind: boolean -} - -export function bindComponentAttr(node: ts.ExpressionStatement, identifierNode: ts.Identifier, - newStatements: ts.Statement[], log: LogInfo[], reverse: boolean = true): 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 (ts.isPropertyAccessExpression(temp.expression) && - temp.expression.name && ts.isIdentifier(temp.expression.name)) { - addComponentAttr(temp, temp.expression.name, lastStatement, statements, identifierNode, log); - 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); - } - break; - } - } - if (lastStatement.statement && lastStatement.kind) { - statements.push(lastStatement.statement); - } - if (statements.length) { - reverse ? newStatements.push(...statements.reverse()) : newStatements.push(...statements); - } -} - -function addComponentAttr(temp: any, node: ts.Identifier, lastStatement: any, - statements: ts.Statement[], identifierNode: ts.Identifier, log: LogInfo[]): void { - const propName: string = node.getText(); - if (propName === ATTRIBUTE_ANIMATION) { - if (!lastStatement.statement) { - if (!(temp.arguments.length === 1 && - temp.arguments[0].kind === ts.SyntaxKind.NullKeyword)) { - statements.push(ts.factory.createExpressionStatement(createFunction( - ts.factory.createIdentifier(GLOBAL_CONTEXT), node, - // @ts-ignore - [ts.factory.createNull()]))); - } - } else { - statements.push(lastStatement.statement); - } - lastStatement.statement = ts.factory.createExpressionStatement(createFunction( - ts.factory.createIdentifier(GLOBAL_CONTEXT), node, temp.arguments)); - lastStatement.kind = false; - } else if (GESTURE_ATTRS.has(propName)) { - parseGesture(temp, propName, statements, log); - lastStatement.kind = true; - } else if (isExtendFunctionNode(identifierNode, propName)) { - validateExtendParameterCount(temp, identifierNode, propName, log); - statements.push(ts.factory.createExpressionStatement(ts.factory.createCallExpression( - ts.factory.createIdentifier(`__${identifierNode.escapedText.toString()}__${propName}`), - undefined, temp.arguments))); - lastStatement.kind = true; - } else { - statements.push(ts.factory.createExpressionStatement( - createFunction(identifierNode, node, temp.arguments))); - lastStatement.kind = true; - } -} - -function isExtendFunctionNode(identifierNode: ts.Identifier, propName: string): boolean { - if (identifierNode && EXTEND_ATTRIBUTE.has(identifierNode.escapedText.toString())) { - const attributeArray: string[] = - [...EXTEND_ATTRIBUTE.get(identifierNode.escapedText.toString())].map(item => item.attribute); - if (attributeArray.includes(propName)) { - return true; - } - } - return false; -} - -const gestureMap: Map = new Map([ - [PRIORITY_GESTURE_ATTRIBUTE, GESTURE_ENUM_VALUE_HIGH], - [PARALLEL_GESTURE_ATTRIBUTE, GESTURE_ENUM_VALUE_PARALLEL], - [GESTURE_ATTRIBUTE, GESTURE_ENUM_VALUE_LOW] -]); - -function parseGesture(node: ts.CallExpression, propName: string, statements: ts.Statement[], - log: LogInfo[]): void { - statements.push(ts.factory.createExpressionStatement( - createFunction(ts.factory.createIdentifier(COMPONENT_GESTURE), - ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); - parseGestureInterface(node, statements, log); - const argumentArr: ts.NodeArray = ts.factory.createNodeArray( - [ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(GESTURE_ENUM_KEY), - ts.factory.createIdentifier(gestureMap.get(propName))) - ] - ); - if (node.arguments && node.arguments.length > 1 && - ts.isPropertyAccessExpression(node.arguments[1])) { - // @ts-ignore - argumentArr.push(node.arguments[1]); - } - statements.push(ts.factory.createExpressionStatement( - createFunction(ts.factory.createIdentifier(COMPONENT_GESTURE), - ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), argumentArr))); -} - -function processGestureType(node: ts.CallExpression, statements: ts.Statement[], log: LogInfo[], - reverse: boolean = false): void { - const newStatements: ts.Statement[] = []; - const newNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(node); - let temp: any = node.expression; - while (temp && !ts.isIdentifier(temp) && temp.expression) { - temp = temp.expression; - } - if (temp && temp.parent && ts.isCallExpression(temp.parent) && ts.isIdentifier(temp) && - GESTURE_TYPE_NAMES.has(temp.escapedText.toString())) { - newStatements.push(ts.factory.createExpressionStatement( - createFunction(temp, ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); - if (temp.escapedText.toString() === COMPONENT_GESTURE_GROUP) { - const gestureStatements: ts.Statement[] = []; - parseGestureInterface(temp.parent, gestureStatements, log, true); - newStatements.push(...gestureStatements.reverse()); - bindComponentAttr(newNode, temp, newStatements, log, false); - let argumentArr: ts.NodeArray = null; - if (temp.parent.arguments && temp.parent.arguments.length) { - // @ts-ignore - argumentArr = ts.factory.createNodeArray([temp.parent.arguments[0]]); - } - newStatements.push(ts.factory.createExpressionStatement( - createFunction(temp, ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), argumentArr))); - } else { - bindComponentAttr(newNode, temp, newStatements, log, false); - newStatements.push(ts.factory.createExpressionStatement( - createFunction(temp, ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), temp.parent.arguments))); - } - } - if (newStatements.length) { - reverse ? statements.push(...newStatements.reverse()) : statements.push(...newStatements); - } -} - -function parseGestureInterface(node: ts.CallExpression, statements: ts.Statement[], log: LogInfo[], - reverse: boolean = false): void { - if (node.arguments && node.arguments.length) { - node.arguments.forEach((item: ts.Node) => { - if (ts.isCallExpression(item)) { - processGestureType(item, statements, log, reverse); - } - }); - } -} - -export function getName(node: ts.ExpressionStatement): string { - let temp: any = node.expression; - let name: string; - while (temp) { - if (ts.isIdentifier(temp) && temp.parent && ts.isCallExpression(temp.parent)) { - name = temp.escapedText.toString(); - break; - } else if (ts.isPropertyAccessExpression(temp) && temp.name && ts.isIdentifier(temp.name) && - !BUILDIN_STYLE_NAMES.has(temp.name.escapedText.toString())) { - name = temp.name.escapedText.toString(); - break; - } - temp = temp.expression; - } - return name; -} - -export function isAttributeNode(node: ts.ExpressionStatement): boolean { - let temp: any = node.expression; - let name: string; - while (temp) { - if (ts.isCallExpression(temp) && temp.expression && ts.isIdentifier(temp.expression)) { - name = temp.expression.escapedText.toString(); - break; - } - temp = temp.expression; - } - return BUILDIN_STYLE_NAMES.has(name); -} - -function validateFirstNode(node: ts.Statement): boolean { - const isEntryComponent: boolean = - componentCollection.entryComponent === componentCollection.currentClassName; - if ((isEntryComponent && validateEntryComponent(node)) || - (!isEntryComponent && validateCustomComponent(node))) { - return true; - } - return false; -} - -function validateEntryComponent(node: ts.Statement): boolean { - if (ts.isExpressionStatement(node) && BUILDIN_CONTAINER_COMPONENT.has(getName(node))) { - return true; - } - return false; -} - -function validateCustomComponent(node: ts.Statement): boolean { - if (ts.isIfStatement(node) || - (ts.isExpressionStatement(node) && (INNER_COMPONENT_NAMES.has(getName(node)) || - componentCollection.customComponents.has(getName(node))))) { - return true; - } - return false; -} - -function validateBlockNode(node: ts.Statement): boolean { - if (ts.isBlock(node)) { - return true; - } - return false; -} - -function validateSecondNode(node: ts.Statement): boolean { - if (ts.isExpressionStatement(node) && isAttributeNode(node)) { - return true; - } - return false; -} - -enum ComponentType { - innerComponent, - customComponent, - forEachComponent, - customBuilderMethod -} - -function getComponentType(node: ts.ExpressionStatement, log: LogInfo[]): ComponentType { - const name: string = getName(node); - if (INNER_COMPONENT_NAMES.has(name)) { - return ComponentType.innerComponent; - } else if (componentCollection.customComponents.has(name)) { - return ComponentType.customComponent; - } else if (name === COMPONENT_FOREACH || name === COMPONENT_LAZYFOREACH) { - appComponentCollection.add(name); - return ComponentType.forEachComponent; - } else if (CUSTOM_BUILDER_METHOD.has(name)) { - return ComponentType.customBuilderMethod; - } else if (!isAttributeNode(node)) { - log.push({ - type: LogType.ERROR, - message: `'${node.getText()}' does not meet UI component syntax.`, - pos: node.getStart() - }); - } - return null; -} - -function validateExtendParameterCount(temp: any, identifierNode: ts.Identifier, propName: string, - log: LogInfo[]): void { - const parameterCount: number = - [...EXTEND_ATTRIBUTE.get(identifierNode.escapedText.toString())].filter(item => - item.attribute === propName)[0].parameterCount; - if (temp.arguments && temp.arguments.length !== parameterCount) { - log.push({ - type: LogType.ERROR, - message: `The '${propName}' is expected ${parameterCount} arguments, but got ${temp.arguments.length}.`, - pos: temp.getStart() - }); - } -} +/* + * 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 { + COMPONENT_RENDER_FUNCTION, + COMPONENT_CREATE_FUNCTION, + COMPONENT_POP_FUNCTION, + COMPONENT_BUTTON, + COMPONENT_BLANK, + COMPONENT_CREATE_LABEL_FUNCTION, + COMPONENT_CREATE_CHILD_FUNCTION, + COMPONENT_FOREACH, + COMPONENT_LAZYFOREACH, + IS_RENDERING_IN_PROGRESS, + FOREACH_OBSERVED_OBJECT, + FOREACH_GET_RAW_OBJECT, + COMPONENT_IF, + COMPONENT_IF_BRANCH_ID_FUNCTION, + COMPONENT_IF_UNDEFINED, + ATTRIBUTE_ANIMATION, + GLOBAL_CONTEXT, + COMPONENT_GESTURE, + COMPONENT_GESTURE_GROUP, + GESTURE_ATTRIBUTE, + PARALLEL_GESTURE_ATTRIBUTE, + PRIORITY_GESTURE_ATTRIBUTE, + GESTURE_ENUM_KEY, + GESTURE_ENUM_VALUE_HIGH, + GESTURE_ENUM_VALUE_LOW, + GESTURE_ENUM_VALUE_PARALLEL, + COMPONENT_TRANSITION_NAME, + COMPONENT_DEBUGLINE_FUNCTION +} from './pre_define'; +import { + INNER_COMPONENT_NAMES, + BUILDIN_CONTAINER_COMPONENT, + BUILDIN_STYLE_NAMES, + CUSTOM_BUILDER_METHOD, + GESTURE_ATTRS, + GESTURE_TYPE_NAMES, + EXTEND_ATTRIBUTE +} from './component_map'; +import { componentCollection } from './validate_ui_syntax'; +import { processCustomComponent } from './process_custom_component'; +import { + LogType, + LogInfo, + componentInfo, + createFunction +} from './utils'; +import { projectConfig } from '../main'; +import { transformLog } from './process_ui_syntax'; + +export const appComponentCollection: Set = new Set(); + +export function processComponentBuild(node: ts.MethodDeclaration, + log: LogInfo[]): ts.MethodDeclaration { + let newNode: ts.MethodDeclaration; + const renderNode: ts.Identifier = ts.factory.createIdentifier(COMPONENT_RENDER_FUNCTION); + if (node.body && node.body.statements && node.body.statements.length && + validateRootNode(node, log)) { + newNode = ts.factory.updateMethodDeclaration(node, node.decorators, node.modifiers, + node.asteriskToken, renderNode, node.questionToken, node.typeParameters, node.parameters, + node.type, processComponentBlock(node.body, false, log)); + } else { + newNode = ts.factory.updateMethodDeclaration(node, node.decorators, node.modifiers, + node.asteriskToken, renderNode, node.questionToken, node.typeParameters, node.parameters, + node.type, node.body); + } + return newNode; +} + +export function processComponentBlock(node: ts.Block, isLazy: boolean, log: LogInfo[], + isTransition: boolean = false): ts.Block { + const newStatements: ts.Statement[] = []; + processComponentChild(node, newStatements, log); + if (isLazy) { + newStatements.unshift(createRenderingInProgress(true)); + } + if (isTransition) { + newStatements.unshift(ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_TRANSITION_NAME), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), null))); + newStatements.push(ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_TRANSITION_NAME), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); + } + if (isLazy) { + newStatements.push(createRenderingInProgress(false)); + } + return ts.factory.updateBlock(node, newStatements); +} + +function validateRootNode(node: ts.MethodDeclaration, log: LogInfo[]): boolean { + let isValid: boolean = false; + if (node.body.statements.length < 4) { + switch (node.body.statements.length) { + case 1: + if (validateFirstNode(node.body.statements[0])) { + isValid = true; + } + break; + case 2: + if (validateFirstNode(node.body.statements[0]) && + validateBlockNode(node.body.statements[1])) { + isValid = true; + } + break; + case 3: + if (validateFirstNode(node.body.statements[0]) && + validateBlockNode(node.body.statements[1]) && + validateSecondNode(node.body.statements[2])) { + isValid = true; + } + break; + } + } + if (!isValid) { + log.push({ + type: LogType.ERROR, + message: `There should have a root container component.`, + pos: node.body.statements.pos + }); + } + return isValid; +} + +function processComponentChild(node: ts.Block, newStatements: ts.Statement[], + log: LogInfo[]): void { + if (node.statements.length) { + node.statements.forEach((item, index) => { + if (ts.isExpressionStatement(item)) { + switch (getComponentType(item, log)) { + case ComponentType.innerComponent: + processInnerComponent(item, index, Array.from(node.statements), newStatements, log); + break; + case ComponentType.customComponent: + processCustomComponent(item, newStatements, log); + break; + case ComponentType.forEachComponent: + processForEachComponent(item, newStatements, log); + break; + case ComponentType.customBuilderMethod: + newStatements.push(item); + break; + } + } else if (ts.isIfStatement(item)) { + appComponentCollection.add(COMPONENT_IF); + processIfStatement(item, newStatements, log); + } else if (!ts.isBlock(item)) { + log.push({ + type: LogType.ERROR, + message: `Only UI component syntax can be written in build method.`, + pos: item.getStart() + }); + } + }); + } +} + +function processInnerComponent(node: ts.ExpressionStatement, index: number, arr: ts.Statement[], + newStatements: ts.Statement[], log: LogInfo[]): void { + const res: CreateResult = createComponent(node, COMPONENT_CREATE_FUNCTION); + newStatements.push(res.newNode); + if (projectConfig.isPreview) { + const posOfNode: ts.LineAndCharacter = + transformLog.sourceFile.getLineAndCharacterOfPosition(node.getStart()); + const projectPath: string = projectConfig.projectPath; + const curFileName: string = transformLog.sourceFile.fileName.replace(/.ts$/, ''); + const debugInfo: string = + `${path.relative(projectPath, curFileName).replace(/\\+/g, '/')}` + + `(${posOfNode.line + 1}:${posOfNode.character + 1})`; + const debugNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(getName(node)), + ts.factory.createIdentifier(COMPONENT_DEBUGLINE_FUNCTION), + ts.factory.createNodeArray([ts.factory.createStringLiteral(debugInfo)]))); + newStatements.push(debugNode); + } + if (index + 1 < arr.length && ts.isBlock(arr[index + 1])) { + if (res.isButton) { + if (projectConfig.isPreview) { + newStatements.splice(-2, 1, createComponent(node, COMPONENT_CREATE_CHILD_FUNCTION).newNode); + } else { + newStatements.splice(-1, 1, createComponent(node, COMPONENT_CREATE_CHILD_FUNCTION).newNode); + } + } + if (index + 2 < arr.length && ts.isExpressionStatement(arr[index + 2]) && + isAttributeNode(arr[index + 2] as ts.ExpressionStatement)) { + bindComponentAttr(arr[index + 2] as ts.ExpressionStatement, res.identifierNode, newStatements, log); + } + processComponentChild(arr[index + 1] as ts.Block, newStatements, log); + } else { + bindComponentAttr(node, res.identifierNode, newStatements, log); + } + if (res.isContainerComponent || res.needPop) { + newStatements.push(createComponent(node, COMPONENT_POP_FUNCTION).newNode); + } +} + +function processForEachComponent(node: ts.ExpressionStatement, newStatements: ts.Statement[], + log: LogInfo[]): void { + const popNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(createFunction( + // @ts-ignore + node.expression.expression as ts.Identifier, + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)); + if (ts.isCallExpression(node.expression)) { + const propertyNode: ts.PropertyAccessExpression = ts.factory.createPropertyAccessExpression( + node.expression.expression as ts.Identifier, + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION) + ); + const argumentsArray: ts.Expression[] = Array.from(node.expression.arguments); + let arrayObserveredObject: ts.CallExpression; + if (argumentsArray.length) { + arrayObserveredObject = ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(FOREACH_OBSERVED_OBJECT), + ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [argumentsArray[0]]); + } + argumentsArray.splice(0, 1, arrayObserveredObject); + const newArrowNode: ts.ArrowFunction = processForEachBlock(node.expression, log); + if (newArrowNode) { + argumentsArray.splice(1, 1, newArrowNode); + } + node = addForEachId(ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression( + node.expression, propertyNode, node.expression.typeArguments, argumentsArray))); + } + newStatements.push(node, popNode); +} + +function addForEachId(node: ts.ExpressionStatement): ts.ExpressionStatement { + const forEachComponent: ts.CallExpression = node.expression as ts.CallExpression; + return ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression( + forEachComponent, forEachComponent.expression, forEachComponent.typeArguments, + [ts.factory.createStringLiteral((++componentInfo.id).toString()), ts.factory.createThis(), + ...forEachComponent.arguments])); +} + +function processForEachBlock(node: ts.CallExpression, log: LogInfo[]): ts.ArrowFunction { + if (node.arguments.length > 1 && ts.isArrowFunction(node.arguments[1])) { + const isLazy: boolean = node.expression.getText() === COMPONENT_LAZYFOREACH; + const arrowNode: ts.ArrowFunction = node.arguments[1] as ts.ArrowFunction; + const body: ts.ConciseBody = arrowNode.body; + if (node.arguments.length > 2 && !ts.isArrowFunction(node.arguments[2])) { + log.push({ + type: LogType.ERROR, + message: 'There should be wrapped in curly braces in ForEach.', + pos: body.getStart() + }); + } else if (!ts.isBlock(body)) { + const statement: ts.Statement = ts.factory.createExpressionStatement(body); + const blockNode: ts.Block = ts.factory.createBlock([statement], true); + // @ts-ignore + statement.parent = blockNode; + return ts.factory.updateArrowFunction( + arrowNode, arrowNode.modifiers, arrowNode.typeParameters, arrowNode.parameters, + arrowNode.type, arrowNode.equalsGreaterThanToken, processComponentBlock(blockNode, isLazy, log)); + } else { + return ts.factory.updateArrowFunction( + arrowNode, arrowNode.modifiers, arrowNode.typeParameters, arrowNode.parameters, + arrowNode.type, arrowNode.equalsGreaterThanToken, processComponentBlock(body, isLazy, log)); + } + } + return null; +} + +function createRenderingInProgress(isTrue: boolean): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(IS_RENDERING_IN_PROGRESS) + ), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + isTrue ? ts.factory.createTrue() : ts.factory.createFalse() + )); +} + +function processIfStatement(node: ts.IfStatement, newStatements: ts.Statement[], + log: LogInfo[]): void { + const ifCreate: ts.ExpressionStatement = createIfCreate(); + const newIfNode: ts.IfStatement = processInnerIfStatement(node, 0, log); + const ifPop: ts.ExpressionStatement = createIfPop(); + newStatements.push(ifCreate, newIfNode, ifPop); +} + +function processInnerIfStatement(node: ts.IfStatement, id: number, log: LogInfo[]): ts.IfStatement { + if (ts.isIdentifier(node.expression) && node.expression.originalKeywordKind === undefined && + !node.expression.escapedText) { + log.push({ + type: LogType.ERROR, + message: 'Condition expression cannot be null in if statement.', + pos: node.expression.getStart() + }); + node = ts.factory.updateIfStatement(node, ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED), + node.thenStatement, node.elseStatement); + } + const newThenStatement: ts.Statement = processThenStatement(node.thenStatement, id, log); + const newElseStatement: ts.Statement = processElseStatement(node.elseStatement, id, log); + const newIfNode: ts.IfStatement = ts.factory.updateIfStatement( + node, node.expression, newThenStatement, newElseStatement); + return newIfNode; +} + +function processThenStatement(thenStatement: ts.Statement, id: number, + log: LogInfo[]): ts.Statement { + if (ts.isExpressionStatement(thenStatement) && ts.isIdentifier(thenStatement.expression) && + thenStatement.expression.originalKeywordKind === undefined && + !thenStatement.expression.escapedText) { + log.push({ + type: LogType.ERROR, + message: 'Then statement cannot be null in if statement.', + pos: thenStatement.expression.getStart() + }); + } + if (thenStatement) { + if (ts.isBlock(thenStatement)) { + thenStatement = processIfBlock(thenStatement, id, log); + } else if (ts.isIfStatement(thenStatement)) { + thenStatement = processInnerIfStatement(thenStatement, 0, log); + thenStatement = ts.factory.createBlock( + [createIfCreate(), createIfBranchId(id), thenStatement, createIfPop()], true); + } else { + thenStatement = ts.factory.createBlock([thenStatement], true); + thenStatement = processIfBlock(thenStatement as ts.Block, id, log); + } + } + return thenStatement; +} + +function processElseStatement(elseStatement: ts.Statement, id: number, + log: LogInfo[]): ts.Statement { + if (elseStatement) { + if (ts.isBlock(elseStatement)) { + elseStatement = processIfBlock(elseStatement, id + 1, log); + } else if (ts.isIfStatement(elseStatement)) { + elseStatement = processInnerIfStatement(elseStatement, id + 1, log); + } else { + elseStatement = ts.factory.createBlock([elseStatement], true); + elseStatement = processIfBlock(elseStatement as ts.Block, id + 1, log); + } + } + return elseStatement; +} + +function processIfBlock(block: ts.Block, id: number, log: LogInfo[]): ts.Block { + return addIfBranchId(id, processComponentBlock(block, false, log)); +} + +function addIfBranchId(id: number, container: ts.Block): ts.Block { + return ts.factory.updateBlock(container, [createIfBranchId(id), ...container.statements]); +} + +function createIf(): ts.Identifier { + return ts.factory.createIdentifier(COMPONENT_IF); +} + +function createIfCreate(): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(createFunction(createIf(), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), ts.factory.createNodeArray([]))); +} + +function createIfPop(): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(createFunction(createIf(), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null)); +} + +function createIfBranchId(id: number): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(createFunction(createIf(), + ts.factory.createIdentifier(COMPONENT_IF_BRANCH_ID_FUNCTION), + ts.factory.createNodeArray([ts.factory.createNumericLiteral(id)]))); +} + +interface CreateResult { + newNode: ts.ExpressionStatement; + identifierNode: ts.Identifier; + isContainerComponent: boolean; + isButton: boolean; + needPop: boolean; +} + +function createComponent(node: ts.ExpressionStatement, type: string): CreateResult { + const res: CreateResult = { + newNode: node, + identifierNode: null, + isContainerComponent: false, + isButton: false, + needPop: false + }; + let identifierNode: ts.Identifier = ts.factory.createIdentifier(type); + let temp: any = node.expression; + while (temp && !ts.isIdentifier(temp) && temp.expression) { + temp = temp.expression; + } + if (temp && temp.parent && ts.isCallExpression(temp.parent) && ts.isIdentifier(temp)) { + if (temp.getText() === COMPONENT_BUTTON && type !== COMPONENT_POP_FUNCTION) { + res.isButton = true; + identifierNode = type === COMPONENT_CREATE_CHILD_FUNCTION + ? ts.factory.createIdentifier(COMPONENT_CREATE_CHILD_FUNCTION) + : ts.factory.createIdentifier(COMPONENT_CREATE_LABEL_FUNCTION); + } + if (temp.getText() === COMPONENT_BLANK) { + res.needPop = true; + } + if (BUILDIN_CONTAINER_COMPONENT.has(temp.getText())) { + res.isContainerComponent = true; + } + res.newNode = type === COMPONENT_POP_FUNCTION + ? ts.factory.updateExpressionStatement(node, + createFunction(temp, identifierNode, null)) + : ts.factory.updateExpressionStatement(node, + createFunction(temp, identifierNode, temp.parent.arguments)); + res.identifierNode = temp; + } + return res; +} + +interface AnimationInfo { + statement: ts.Statement, + kind: boolean +} + +export function bindComponentAttr(node: ts.ExpressionStatement, identifierNode: ts.Identifier, + newStatements: ts.Statement[], log: LogInfo[], reverse: boolean = true): 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 (ts.isPropertyAccessExpression(temp.expression) && + temp.expression.name && ts.isIdentifier(temp.expression.name)) { + addComponentAttr(temp, temp.expression.name, lastStatement, statements, identifierNode, log); + 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); + } + break; + } + } + if (lastStatement.statement && lastStatement.kind) { + statements.push(lastStatement.statement); + } + if (statements.length) { + reverse ? newStatements.push(...statements.reverse()) : newStatements.push(...statements); + } +} + +function addComponentAttr(temp: any, node: ts.Identifier, lastStatement: any, + statements: ts.Statement[], identifierNode: ts.Identifier, log: LogInfo[]): void { + const propName: string = node.getText(); + if (propName === ATTRIBUTE_ANIMATION) { + if (!lastStatement.statement) { + if (!(temp.arguments.length === 1 && + temp.arguments[0].kind === ts.SyntaxKind.NullKeyword)) { + statements.push(ts.factory.createExpressionStatement(createFunction( + ts.factory.createIdentifier(GLOBAL_CONTEXT), node, + // @ts-ignore + [ts.factory.createNull()]))); + } + } else { + statements.push(lastStatement.statement); + } + lastStatement.statement = ts.factory.createExpressionStatement(createFunction( + ts.factory.createIdentifier(GLOBAL_CONTEXT), node, temp.arguments)); + lastStatement.kind = false; + } else if (GESTURE_ATTRS.has(propName)) { + parseGesture(temp, propName, statements, log); + lastStatement.kind = true; + } else if (isExtendFunctionNode(identifierNode, propName)) { + validateExtendParameterCount(temp, identifierNode, propName, log); + statements.push(ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createIdentifier(`__${identifierNode.escapedText.toString()}__${propName}`), + undefined, temp.arguments))); + lastStatement.kind = true; + } else { + statements.push(ts.factory.createExpressionStatement( + createFunction(identifierNode, node, temp.arguments))); + lastStatement.kind = true; + } +} + +function isExtendFunctionNode(identifierNode: ts.Identifier, propName: string): boolean { + if (identifierNode && EXTEND_ATTRIBUTE.has(identifierNode.escapedText.toString())) { + const attributeArray: string[] = + [...EXTEND_ATTRIBUTE.get(identifierNode.escapedText.toString())].map(item => item.attribute); + if (attributeArray.includes(propName)) { + return true; + } + } + return false; +} + +const gestureMap: Map = new Map([ + [PRIORITY_GESTURE_ATTRIBUTE, GESTURE_ENUM_VALUE_HIGH], + [PARALLEL_GESTURE_ATTRIBUTE, GESTURE_ENUM_VALUE_PARALLEL], + [GESTURE_ATTRIBUTE, GESTURE_ENUM_VALUE_LOW] +]); + +function parseGesture(node: ts.CallExpression, propName: string, statements: ts.Statement[], + log: LogInfo[]): void { + statements.push(ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_GESTURE), + ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); + parseGestureInterface(node, statements, log); + const argumentArr: ts.NodeArray = ts.factory.createNodeArray( + [ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(GESTURE_ENUM_KEY), + ts.factory.createIdentifier(gestureMap.get(propName))) + ] + ); + if (node.arguments && node.arguments.length > 1 && + ts.isPropertyAccessExpression(node.arguments[1])) { + // @ts-ignore + argumentArr.push(node.arguments[1]); + } + statements.push(ts.factory.createExpressionStatement( + createFunction(ts.factory.createIdentifier(COMPONENT_GESTURE), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), argumentArr))); +} + +function processGestureType(node: ts.CallExpression, statements: ts.Statement[], log: LogInfo[], + reverse: boolean = false): void { + const newStatements: ts.Statement[] = []; + const newNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(node); + let temp: any = node.expression; + while (temp && !ts.isIdentifier(temp) && temp.expression) { + temp = temp.expression; + } + if (temp && temp.parent && ts.isCallExpression(temp.parent) && ts.isIdentifier(temp) && + GESTURE_TYPE_NAMES.has(temp.escapedText.toString())) { + newStatements.push(ts.factory.createExpressionStatement( + createFunction(temp, ts.factory.createIdentifier(COMPONENT_POP_FUNCTION), null))); + if (temp.escapedText.toString() === COMPONENT_GESTURE_GROUP) { + const gestureStatements: ts.Statement[] = []; + parseGestureInterface(temp.parent, gestureStatements, log, true); + newStatements.push(...gestureStatements.reverse()); + bindComponentAttr(newNode, temp, newStatements, log, false); + let argumentArr: ts.NodeArray = null; + if (temp.parent.arguments && temp.parent.arguments.length) { + // @ts-ignore + argumentArr = ts.factory.createNodeArray([temp.parent.arguments[0]]); + } + newStatements.push(ts.factory.createExpressionStatement( + createFunction(temp, ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), argumentArr))); + } else { + bindComponentAttr(newNode, temp, newStatements, log, false); + newStatements.push(ts.factory.createExpressionStatement( + createFunction(temp, ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), temp.parent.arguments))); + } + } + if (newStatements.length) { + reverse ? statements.push(...newStatements.reverse()) : statements.push(...newStatements); + } +} + +function parseGestureInterface(node: ts.CallExpression, statements: ts.Statement[], log: LogInfo[], + reverse: boolean = false): void { + if (node.arguments && node.arguments.length) { + node.arguments.forEach((item: ts.Node) => { + if (ts.isCallExpression(item)) { + processGestureType(item, statements, log, reverse); + } + }); + } +} + +export function getName(node: ts.ExpressionStatement): string { + let temp: any = node.expression; + let name: string; + while (temp) { + if (ts.isIdentifier(temp) && temp.parent && ts.isCallExpression(temp.parent)) { + name = temp.escapedText.toString(); + break; + } else if (ts.isPropertyAccessExpression(temp) && temp.name && ts.isIdentifier(temp.name) && + !BUILDIN_STYLE_NAMES.has(temp.name.escapedText.toString())) { + name = temp.name.escapedText.toString(); + break; + } + temp = temp.expression; + } + return name; +} + +export function isAttributeNode(node: ts.ExpressionStatement): boolean { + let temp: any = node.expression; + let name: string; + while (temp) { + if (ts.isCallExpression(temp) && temp.expression && ts.isIdentifier(temp.expression)) { + name = temp.expression.escapedText.toString(); + break; + } + temp = temp.expression; + } + return BUILDIN_STYLE_NAMES.has(name); +} + +function validateFirstNode(node: ts.Statement): boolean { + const isEntryComponent: boolean = + componentCollection.entryComponent === componentCollection.currentClassName; + if ((isEntryComponent && validateEntryComponent(node)) || + (!isEntryComponent && validateCustomComponent(node))) { + return true; + } + return false; +} + +function validateEntryComponent(node: ts.Statement): boolean { + if (ts.isExpressionStatement(node) && BUILDIN_CONTAINER_COMPONENT.has(getName(node))) { + return true; + } + return false; +} + +function validateCustomComponent(node: ts.Statement): boolean { + if (ts.isIfStatement(node) || + (ts.isExpressionStatement(node) && (INNER_COMPONENT_NAMES.has(getName(node)) || + componentCollection.customComponents.has(getName(node))))) { + return true; + } + return false; +} + +function validateBlockNode(node: ts.Statement): boolean { + if (ts.isBlock(node)) { + return true; + } + return false; +} + +function validateSecondNode(node: ts.Statement): boolean { + if (ts.isExpressionStatement(node) && isAttributeNode(node)) { + return true; + } + return false; +} + +enum ComponentType { + innerComponent, + customComponent, + forEachComponent, + customBuilderMethod +} + +function getComponentType(node: ts.ExpressionStatement, log: LogInfo[]): ComponentType { + const name: string = getName(node); + if (INNER_COMPONENT_NAMES.has(name)) { + return ComponentType.innerComponent; + } else if (componentCollection.customComponents.has(name)) { + return ComponentType.customComponent; + } else if (name === COMPONENT_FOREACH || name === COMPONENT_LAZYFOREACH) { + appComponentCollection.add(name); + return ComponentType.forEachComponent; + } else if (CUSTOM_BUILDER_METHOD.has(name)) { + return ComponentType.customBuilderMethod; + } else if (!isAttributeNode(node)) { + log.push({ + type: LogType.ERROR, + message: `'${node.getText()}' does not meet UI component syntax.`, + pos: node.getStart() + }); + } + return null; +} + +function validateExtendParameterCount(temp: any, identifierNode: ts.Identifier, propName: string, + log: LogInfo[]): void { + const parameterCount: number = + [...EXTEND_ATTRIBUTE.get(identifierNode.escapedText.toString())].filter(item => + item.attribute === propName)[0].parameterCount; + if (temp.arguments && temp.arguments.length !== parameterCount) { + log.push({ + type: LogType.ERROR, + message: `The '${propName}' is expected ${parameterCount} arguments, but got ${temp.arguments.length}.`, + pos: temp.getStart() + }); + } +} diff --git a/compiler/src/process_component_class.ts b/compiler/src/process_component_class.ts index 9fa5dfe02a870035f9114b40671dddff4e48c77e..c5429593d1811feba4e98b098f16c2116caf3729 100644 --- a/compiler/src/process_component_class.ts +++ b/compiler/src/process_component_class.ts @@ -1,400 +1,400 @@ -/* - * 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 { - COMPONENT_BUILD_FUNCTION, - BASE_COMPONENT_NAME, - ATTRIBUTE_ANIMATETO, - GLOBAL_CONTEXT, - CREATE_CONSTRUCTOR_PARAMS, - COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, - COMPONENT_CONSTRUCTOR_DELETE_PARAMS, - CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER, - ABOUT_TO_BE_DELETE_FUNCTION_ID, - CREATE_CONSTRUCTOR_GET_FUNCTION, - CREATE_CONSTRUCTOR_DELETE_FUNCTION, - FOREACH_OBSERVED_OBJECT, - FOREACH_GET_RAW_OBJECT, - COMPONENT_BUILDER_DECORATOR, - COMPONENT_TRANSITION_FUNCTION, - COMPONENT_CREATE_FUNCTION, - GEOMETRY_VIEW -} from './pre_define'; -import { - BUILDIN_STYLE_NAMES, - CUSTOM_BUILDER_METHOD -} from './component_map'; -import { - componentCollection, - linkCollection -} from './validate_ui_syntax'; -import { - addConstructor, - getInitConstructor -} from './process_component_constructor'; -import { - ControllerType, - processMemberVariableDecorators, - UpdateResult, - stateObjectCollection, - curPropMap -} from './process_component_member'; -import { - processComponentBuild, - processComponentBlock -} from './process_component_build'; -import { - LogType, - LogInfo, - hasDecorator -} from './utils'; - -export function processComponentClass(node: ts.ClassDeclaration, context: ts.TransformationContext, - log: LogInfo[], program: ts.Program): ts.ClassDeclaration { - validateInheritClass(node, log); - const memberNode: ts.ClassElement[] = - processMembers(node.members, node.name, context, log, program); - return ts.factory.updateClassDeclaration(node, undefined, node.modifiers, node.name, - node.typeParameters, updateHeritageClauses(node), memberNode); -} - -type BuildCount = { - count: number; -} - -function processMembers(members: ts.NodeArray, parentComponentName: ts.Identifier, - context: ts.TransformationContext, log: LogInfo[], program: ts.Program): ts.ClassElement[] { - const buildCount: BuildCount = { count: 0 }; - let ctorNode: any = getInitConstructor(members); - const newMembers: ts.ClassElement[] = []; - const watchMap: Map = new Map(); - const updateParamsStatements: ts.Statement[] = []; - const deleteParamsStatements: ts.PropertyDeclaration[] = []; - const checkController: ControllerType = - { hasController: !componentCollection.customDialogs.has(parentComponentName.getText()) }; - members.forEach((item: ts.ClassElement) => { - let updateItem: ts.ClassElement; - if (ts.isPropertyDeclaration(item)) { - const result: UpdateResult = processMemberVariableDecorators(parentComponentName, item, - ctorNode, watchMap, checkController, log, program); - if (result.isItemUpdate()) { - updateItem = result.getProperity(); - } else { - updateItem = item; - } - if (result.getVariableGet()) { - newMembers.push(result.getVariableGet()); - } - if (result.getVariableSet()) { - newMembers.push(result.getVariableSet()); - } - if (result.isCtorUpdate()) { - ctorNode = result.getCtor(); - } - if (result.getUpdateParams()) { - updateParamsStatements.push(result.getUpdateParams()); - } - if (result.isDeleteParams()) { - deleteParamsStatements.push(item); - } - if (result.getControllerSet()) { - newMembers.push(result.getControllerSet()); - } - } - if (ts.isMethodDeclaration(item) && item.name) { - updateItem = - processComponentMethod(item, parentComponentName, context, log, buildCount); - } - if (updateItem) { - newMembers.push(updateItem); - } - }); - validateBuildMethodCount(buildCount, parentComponentName, log); - validateHasController(parentComponentName, checkController, log); - newMembers.unshift(addDeleteParamsFunc(deleteParamsStatements)); - newMembers.unshift(addUpdateParamsFunc(updateParamsStatements)); - newMembers.unshift(addConstructor(ctorNode, watchMap)); - return newMembers; -} - -function processComponentMethod(node: ts.MethodDeclaration, parentComponentName: ts.Identifier, - context: ts.TransformationContext, log: LogInfo[], buildCount: BuildCount): ts.MethodDeclaration { - let updateItem: ts.MethodDeclaration = node; - const name: string = node.name.getText(); - if (name === COMPONENT_BUILD_FUNCTION) { - buildCount.count = buildCount.count + 1; - updateItem = processBuildMember(node, context, log); - curPropMap.clear(); - } else if (node.body && ts.isBlock(node.body)) { - if (name === COMPONENT_TRANSITION_FUNCTION) { - updateItem = ts.factory.updateMethodDeclaration(node, node.decorators, node.modifiers, - node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, - node.type, processComponentBlock(node.body, false, log, true)); - } else if (hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { - CUSTOM_BUILDER_METHOD.add(name); - updateItem = ts.factory.updateMethodDeclaration(node, undefined, node.modifiers, - node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, - node.type, processComponentBlock(node.body, false, log)); - } - } - return ts.visitNode(updateItem, visitMethod); - function visitMethod(node: ts.Node): ts.Node { - if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) { - const name: string = node.expression.escapedText.toString(); - if (name === ATTRIBUTE_ANIMATETO) { - node = processAnimateTo(node); - } - } - return ts.visitEachChild(node, visitMethod, context); - } -} - -function processBuildMember(node: ts.MethodDeclaration, context: ts.TransformationContext, - log: LogInfo[]): ts.MethodDeclaration { - if (node.parameters.length) { - log.push({ - type: LogType.ERROR, - message: `The 'build' method can not have arguments.`, - pos: node.getStart() - }); - } - const buildNode: ts.MethodDeclaration = processComponentBuild(node, log); - return ts.visitNode(buildNode, visitBuild); - function visitBuild(node: ts.Node): ts.Node { - if (isGeometryView(node)) { - node = processGeometryView(node as ts.ExpressionStatement, log); - } - if (isProperty(node)) { - node = createReference(node as ts.PropertyAssignment); - } - if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && - stateObjectCollection.has(node.name.escapedText.toString()) && node.parent && - ts.isCallExpression(node.parent) && ts.isPropertyAccessExpression(node.parent.expression) && - node.parent.expression.name.escapedText.toString() !== FOREACH_GET_RAW_OBJECT) { - return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(FOREACH_OBSERVED_OBJECT), - ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [node]); - } - return ts.visitEachChild(node, visitBuild, context); - } -} - -function isGeometryView(node: ts.Node): boolean { - if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) { - const call: ts.CallExpression = node.expression; - const exp: ts.Expression = call.expression; - const args: ts.NodeArray = call.arguments; - if (ts.isPropertyAccessExpression(exp) && ts.isIdentifier(exp.expression) && - exp.expression.escapedText.toString() === GEOMETRY_VIEW && ts.isIdentifier(exp.name) && - exp.name.escapedText.toString() === COMPONENT_CREATE_FUNCTION && args && args.length === 1 && - (ts.isArrowFunction(args[0]) || ts.isFunctionExpression(args[0]))) { - return true; - } - } - return false; -} - -function processGeometryView(node: ts.ExpressionStatement, - log: LogInfo[]): ts.ExpressionStatement { - const exp: ts.CallExpression = node.expression as ts.CallExpression; - const arg: ts.ArrowFunction | ts.FunctionExpression = - exp.arguments[0] as ts.ArrowFunction | ts.FunctionExpression; - return ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression(exp, - exp.expression, undefined, [ts.factory.createArrowFunction(undefined, undefined, arg.parameters, - undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), - getGeometryReaderFunctionBlock(arg, log))])); -} - -function getGeometryReaderFunctionBlock(node: ts.ArrowFunction | ts.FunctionExpression, - log: LogInfo[]): ts.Block { - let blockNode: ts.Block; - if (ts.isBlock(node.body)) { - blockNode = node.body; - } else if (ts.isArrowFunction(node) && ts.isCallExpression(node.body)) { - blockNode = ts.factory.createBlock([ts.factory.createExpressionStatement(node.body)]); - } - return processComponentBlock(blockNode, false, log); -} - -function updateHeritageClauses(node: ts.ClassDeclaration): ts.NodeArray { - const result:ts.HeritageClause[] = []; - const heritageClause:ts.HeritageClause = ts.factory.createHeritageClause( - ts.SyntaxKind.ExtendsKeyword, - [ts.factory.createExpressionWithTypeArguments( - ts.factory.createIdentifier(BASE_COMPONENT_NAME), [])]); - - if (node.heritageClauses) { - result.push(...node.heritageClauses); - } - result.push(heritageClause); - - return ts.factory.createNodeArray(result); -} - -function isProperty(node: ts.Node): Boolean { - if (node.parent && ts.isObjectLiteralExpression(node.parent) && node.parent.parent && - ts.isCallExpression(node.parent.parent) && ts.isPropertyAssignment(node) && - ts.isIdentifier(node.name)) { - if (ts.isIdentifier(node.parent.parent.expression) && - !BUILDIN_STYLE_NAMES.has(node.parent.parent.expression.escapedText.toString()) && - componentCollection.customComponents.has( - node.parent.parent.expression.escapedText.toString())) { - return true; - } else if (ts.isPropertyAccessExpression(node.parent.parent.expression) && - ts.isIdentifier(node.parent.parent.expression.expression) && - componentCollection.customComponents.has( - node.parent.parent.expression.name.escapedText.toString())) { - return true; - } - } - return false; -} - -function createReference(node: ts.PropertyAssignment): ts.PropertyAssignment { - const linkParentComponent: string[] = getParentNode(node, linkCollection).slice(1); - const propertyName: ts.Identifier = node.name as ts.Identifier; - let initText: string; - if (linkParentComponent && ts.isPropertyAssignment(node) && ts.isIdentifier(propertyName) && - linkParentComponent.includes(propertyName.escapedText.toString())) { - const LINK_REG: RegExp = /^\$/g; - const initExpression: ts.Expression = node.initializer; - if (ts.isIdentifier(initExpression) && - initExpression.escapedText.toString().match(LINK_REG)) { - if (linkParentComponent.includes(propertyName.escapedText.toString())) { - initText = initExpression.escapedText.toString().replace(LINK_REG, ''); - } - } else if (ts.isPropertyAccessExpression(initExpression) && initExpression.expression && - initExpression.expression.kind === ts.SyntaxKind.ThisKeyword && - ts.isIdentifier(initExpression.name) && - initExpression.name.escapedText.toString().match(LINK_REG)) { - if (linkParentComponent.includes(propertyName.escapedText.toString())) { - initText = initExpression.name.escapedText.toString().replace(LINK_REG, ''); - } - } - if (initText) { - node = addDoubleUnderline(node, propertyName, initText); - } - } - return node; -} - -function addDoubleUnderline(node: ts.PropertyAssignment, propertyName: ts.Identifier, - initText: string): ts.PropertyAssignment { - return ts.factory.updatePropertyAssignment(node, propertyName, - ts.factory.createPropertyAccessExpression(ts.factory.createThis(), - ts.factory.createIdentifier(`__${initText}`))); -} - -function getParentNode(node: ts.PropertyAssignment, collection: Map>): string[] { - const grandparentNode: ts.NewExpression = node.parent.parent as ts.NewExpression; - const grandparentExpression: ts.Identifier | ts.PropertyAccessExpression = - grandparentNode.expression as ts.Identifier | ts.PropertyAccessExpression; - let parentComponent: Set = new Set(); - let grandparentName: string; - if (ts.isIdentifier(grandparentExpression)) { - grandparentName = grandparentExpression.escapedText.toString(); - parentComponent = collection.get(grandparentName); - } else if (ts.isPropertyAccessExpression(grandparentExpression)) { - grandparentName = grandparentExpression.name.escapedText.toString(); - parentComponent = collection.get(grandparentName); - } else { - // ignore - } - if (!parentComponent) { - parentComponent = new Set(); - } - return [grandparentName, ...parentComponent]; -} - -function processAnimateTo(node: ts.CallExpression): ts.CallExpression { - return ts.factory.updateCallExpression(node, ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(GLOBAL_CONTEXT), ts.factory.createIdentifier(ATTRIBUTE_ANIMATETO)), - node.typeArguments, node.arguments); -} - -function addUpdateParamsFunc(statements: ts.Statement[]): ts.MethodDeclaration { - return createParamsInitBlock(COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, statements); -} - -function addDeleteParamsFunc(statements: ts.PropertyDeclaration[]): ts.MethodDeclaration { - const deleteStatements: ts.ExpressionStatement[] = []; - statements.forEach((statement: ts.PropertyDeclaration) => { - const name: ts.Identifier = statement.name as ts.Identifier; - const paramsStatement: ts.ExpressionStatement = ts.factory.createExpressionStatement( - ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( - ts.factory.createPropertyAccessExpression(ts.factory.createThis(), - ts.factory.createIdentifier(`__${name.escapedText.toString()}`)), - ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_DELETE_PARAMS)), undefined, [])); - deleteStatements.push(paramsStatement); - }); - const defaultStatement: ts.ExpressionStatement = - ts.factory.createExpressionStatement(ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER), - ts.factory.createIdentifier(CREATE_CONSTRUCTOR_GET_FUNCTION)), undefined, []), - ts.factory.createIdentifier(CREATE_CONSTRUCTOR_DELETE_FUNCTION)), - undefined, [ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( - ts.factory.createThis(), ts.factory.createIdentifier(ABOUT_TO_BE_DELETE_FUNCTION_ID)), - undefined, [])])); - deleteStatements.push(defaultStatement); - const deleteParamsMethod: ts.MethodDeclaration = - createParamsInitBlock(COMPONENT_CONSTRUCTOR_DELETE_PARAMS, deleteStatements); - return deleteParamsMethod; -} - -function createParamsInitBlock(express: string, statements: ts.Statement[]): 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)); - return methodDeclaration; -} - -function validateBuildMethodCount(buildCount: BuildCount, parentComponentName: ts.Identifier, - log: LogInfo[]): void { - if (buildCount.count !== 1) { - log.push({ - type: LogType.ERROR, - message: `struct '${parentComponentName.getText()}' must be at least or at most one 'build' method.`, - pos: parentComponentName.getStart() - }); - } -} - -function validateInheritClass(node: ts.ClassDeclaration, log: LogInfo[]): void { - if (node.heritageClauses) { - log.push({ - type: LogType.ERROR, - message: '@Component should not be inherit other Classes.', - pos: node.heritageClauses.pos - }); - } -} - -function validateHasController(componentName: ts.Identifier, checkController: ControllerType, - log: LogInfo[]): void { - if (!checkController.hasController) { - log.push({ - type: LogType.ERROR, - message: '@CustomDialog component should have a property of the CustomDialogController type.', - pos: componentName.pos - }); - } -} +/* + * 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 { + COMPONENT_BUILD_FUNCTION, + BASE_COMPONENT_NAME, + ATTRIBUTE_ANIMATETO, + GLOBAL_CONTEXT, + CREATE_CONSTRUCTOR_PARAMS, + COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, + COMPONENT_CONSTRUCTOR_DELETE_PARAMS, + CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER, + ABOUT_TO_BE_DELETE_FUNCTION_ID, + CREATE_CONSTRUCTOR_GET_FUNCTION, + CREATE_CONSTRUCTOR_DELETE_FUNCTION, + FOREACH_OBSERVED_OBJECT, + FOREACH_GET_RAW_OBJECT, + COMPONENT_BUILDER_DECORATOR, + COMPONENT_TRANSITION_FUNCTION, + COMPONENT_CREATE_FUNCTION, + GEOMETRY_VIEW +} from './pre_define'; +import { + BUILDIN_STYLE_NAMES, + CUSTOM_BUILDER_METHOD +} from './component_map'; +import { + componentCollection, + linkCollection +} from './validate_ui_syntax'; +import { + addConstructor, + getInitConstructor +} from './process_component_constructor'; +import { + ControllerType, + processMemberVariableDecorators, + UpdateResult, + stateObjectCollection, + curPropMap +} from './process_component_member'; +import { + processComponentBuild, + processComponentBlock +} from './process_component_build'; +import { + LogType, + LogInfo, + hasDecorator +} from './utils'; + +export function processComponentClass(node: ts.ClassDeclaration, context: ts.TransformationContext, + log: LogInfo[], program: ts.Program): ts.ClassDeclaration { + validateInheritClass(node, log); + const memberNode: ts.ClassElement[] = + processMembers(node.members, node.name, context, log, program); + return ts.factory.updateClassDeclaration(node, undefined, node.modifiers, node.name, + node.typeParameters, updateHeritageClauses(node), memberNode); +} + +type BuildCount = { + count: number; +} + +function processMembers(members: ts.NodeArray, parentComponentName: ts.Identifier, + context: ts.TransformationContext, log: LogInfo[], program: ts.Program): ts.ClassElement[] { + const buildCount: BuildCount = { count: 0 }; + let ctorNode: any = getInitConstructor(members); + const newMembers: ts.ClassElement[] = []; + const watchMap: Map = new Map(); + const updateParamsStatements: ts.Statement[] = []; + const deleteParamsStatements: ts.PropertyDeclaration[] = []; + const checkController: ControllerType = + { hasController: !componentCollection.customDialogs.has(parentComponentName.getText()) }; + members.forEach((item: ts.ClassElement) => { + let updateItem: ts.ClassElement; + if (ts.isPropertyDeclaration(item)) { + const result: UpdateResult = processMemberVariableDecorators(parentComponentName, item, + ctorNode, watchMap, checkController, log, program, context); + if (result.isItemUpdate()) { + updateItem = result.getProperity(); + } else { + updateItem = item; + } + if (result.getVariableGet()) { + newMembers.push(result.getVariableGet()); + } + if (result.getVariableSet()) { + newMembers.push(result.getVariableSet()); + } + if (result.isCtorUpdate()) { + ctorNode = result.getCtor(); + } + if (result.getUpdateParams()) { + updateParamsStatements.push(result.getUpdateParams()); + } + if (result.isDeleteParams()) { + deleteParamsStatements.push(item); + } + if (result.getControllerSet()) { + newMembers.push(result.getControllerSet()); + } + } + if (ts.isMethodDeclaration(item) && item.name) { + updateItem = + processComponentMethod(item, parentComponentName, context, log, buildCount); + } + if (updateItem) { + newMembers.push(updateItem); + } + }); + validateBuildMethodCount(buildCount, parentComponentName, log); + validateHasController(parentComponentName, checkController, log); + newMembers.unshift(addDeleteParamsFunc(deleteParamsStatements)); + newMembers.unshift(addUpdateParamsFunc(updateParamsStatements)); + newMembers.unshift(addConstructor(ctorNode, watchMap)); + return newMembers; +} + +function processComponentMethod(node: ts.MethodDeclaration, parentComponentName: ts.Identifier, + context: ts.TransformationContext, log: LogInfo[], buildCount: BuildCount): ts.MethodDeclaration { + let updateItem: ts.MethodDeclaration = node; + const name: string = node.name.getText(); + if (name === COMPONENT_BUILD_FUNCTION) { + buildCount.count = buildCount.count + 1; + updateItem = processBuildMember(node, context, log); + curPropMap.clear(); + } else if (node.body && ts.isBlock(node.body)) { + if (name === COMPONENT_TRANSITION_FUNCTION) { + updateItem = ts.factory.updateMethodDeclaration(node, node.decorators, node.modifiers, + node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, + node.type, processComponentBlock(node.body, false, log, true)); + } else if (hasDecorator(node, COMPONENT_BUILDER_DECORATOR)) { + CUSTOM_BUILDER_METHOD.add(name); + updateItem = ts.factory.updateMethodDeclaration(node, undefined, node.modifiers, + node.asteriskToken, node.name, node.questionToken, node.typeParameters, node.parameters, + node.type, processComponentBlock(node.body, false, log)); + } + } + return ts.visitNode(updateItem, visitMethod); + function visitMethod(node: ts.Node): ts.Node { + if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) { + const name: string = node.expression.escapedText.toString(); + if (name === ATTRIBUTE_ANIMATETO) { + node = processAnimateTo(node); + } + } + return ts.visitEachChild(node, visitMethod, context); + } +} + +function processBuildMember(node: ts.MethodDeclaration, context: ts.TransformationContext, + log: LogInfo[]): ts.MethodDeclaration { + if (node.parameters.length) { + log.push({ + type: LogType.ERROR, + message: `The 'build' method can not have arguments.`, + pos: node.getStart() + }); + } + const buildNode: ts.MethodDeclaration = processComponentBuild(node, log); + return ts.visitNode(buildNode, visitBuild); + function visitBuild(node: ts.Node): ts.Node { + if (isGeometryView(node)) { + node = processGeometryView(node as ts.ExpressionStatement, log); + } + if (isProperty(node)) { + node = createReference(node as ts.PropertyAssignment); + } + if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name) && + stateObjectCollection.has(node.name.escapedText.toString()) && node.parent && + ts.isCallExpression(node.parent) && ts.isPropertyAccessExpression(node.parent.expression) && + node.parent.expression.name.escapedText.toString() !== FOREACH_GET_RAW_OBJECT) { + return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(FOREACH_OBSERVED_OBJECT), + ts.factory.createIdentifier(FOREACH_GET_RAW_OBJECT)), undefined, [node]); + } + return ts.visitEachChild(node, visitBuild, context); + } +} + +function isGeometryView(node: ts.Node): boolean { + if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) { + const call: ts.CallExpression = node.expression; + const exp: ts.Expression = call.expression; + const args: ts.NodeArray = call.arguments; + if (ts.isPropertyAccessExpression(exp) && ts.isIdentifier(exp.expression) && + exp.expression.escapedText.toString() === GEOMETRY_VIEW && ts.isIdentifier(exp.name) && + exp.name.escapedText.toString() === COMPONENT_CREATE_FUNCTION && args && args.length === 1 && + (ts.isArrowFunction(args[0]) || ts.isFunctionExpression(args[0]))) { + return true; + } + } + return false; +} + +function processGeometryView(node: ts.ExpressionStatement, + log: LogInfo[]): ts.ExpressionStatement { + const exp: ts.CallExpression = node.expression as ts.CallExpression; + const arg: ts.ArrowFunction | ts.FunctionExpression = + exp.arguments[0] as ts.ArrowFunction | ts.FunctionExpression; + return ts.factory.updateExpressionStatement(node, ts.factory.updateCallExpression(exp, + exp.expression, undefined, [ts.factory.createArrowFunction(undefined, undefined, arg.parameters, + undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + getGeometryReaderFunctionBlock(arg, log))])); +} + +function getGeometryReaderFunctionBlock(node: ts.ArrowFunction | ts.FunctionExpression, + log: LogInfo[]): ts.Block { + let blockNode: ts.Block; + if (ts.isBlock(node.body)) { + blockNode = node.body; + } else if (ts.isArrowFunction(node) && ts.isCallExpression(node.body)) { + blockNode = ts.factory.createBlock([ts.factory.createExpressionStatement(node.body)]); + } + return processComponentBlock(blockNode, false, log); +} + +function updateHeritageClauses(node: ts.ClassDeclaration): ts.NodeArray { + const result:ts.HeritageClause[] = []; + const heritageClause:ts.HeritageClause = ts.factory.createHeritageClause( + ts.SyntaxKind.ExtendsKeyword, + [ts.factory.createExpressionWithTypeArguments( + ts.factory.createIdentifier(BASE_COMPONENT_NAME), [])]); + + if (node.heritageClauses) { + result.push(...node.heritageClauses); + } + result.push(heritageClause); + + return ts.factory.createNodeArray(result); +} + +export function isProperty(node: ts.Node): Boolean { + if (node.parent && ts.isObjectLiteralExpression(node.parent) && node.parent.parent && + ts.isCallExpression(node.parent.parent) && ts.isPropertyAssignment(node) && + ts.isIdentifier(node.name)) { + if (ts.isIdentifier(node.parent.parent.expression) && + !BUILDIN_STYLE_NAMES.has(node.parent.parent.expression.escapedText.toString()) && + componentCollection.customComponents.has( + node.parent.parent.expression.escapedText.toString())) { + return true; + } else if (ts.isPropertyAccessExpression(node.parent.parent.expression) && + ts.isIdentifier(node.parent.parent.expression.expression) && + componentCollection.customComponents.has( + node.parent.parent.expression.name.escapedText.toString())) { + return true; + } + } + return false; +} + +export function createReference(node: ts.PropertyAssignment): ts.PropertyAssignment { + const linkParentComponent: string[] = getParentNode(node, linkCollection).slice(1); + const propertyName: ts.Identifier = node.name as ts.Identifier; + let initText: string; + if (linkParentComponent && ts.isPropertyAssignment(node) && ts.isIdentifier(propertyName) && + linkParentComponent.includes(propertyName.escapedText.toString())) { + const LINK_REG: RegExp = /^\$/g; + const initExpression: ts.Expression = node.initializer; + if (ts.isIdentifier(initExpression) && + initExpression.escapedText.toString().match(LINK_REG)) { + if (linkParentComponent.includes(propertyName.escapedText.toString())) { + initText = initExpression.escapedText.toString().replace(LINK_REG, ''); + } + } else if (ts.isPropertyAccessExpression(initExpression) && initExpression.expression && + initExpression.expression.kind === ts.SyntaxKind.ThisKeyword && + ts.isIdentifier(initExpression.name) && + initExpression.name.escapedText.toString().match(LINK_REG)) { + if (linkParentComponent.includes(propertyName.escapedText.toString())) { + initText = initExpression.name.escapedText.toString().replace(LINK_REG, ''); + } + } + if (initText) { + node = addDoubleUnderline(node, propertyName, initText); + } + } + return node; +} + +function addDoubleUnderline(node: ts.PropertyAssignment, propertyName: ts.Identifier, + initText: string): ts.PropertyAssignment { + return ts.factory.updatePropertyAssignment(node, propertyName, + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(`__${initText}`))); +} + +function getParentNode(node: ts.PropertyAssignment, collection: Map>): string[] { + const grandparentNode: ts.NewExpression = node.parent.parent as ts.NewExpression; + const grandparentExpression: ts.Identifier | ts.PropertyAccessExpression = + grandparentNode.expression as ts.Identifier | ts.PropertyAccessExpression; + let parentComponent: Set = new Set(); + let grandparentName: string; + if (ts.isIdentifier(grandparentExpression)) { + grandparentName = grandparentExpression.escapedText.toString(); + parentComponent = collection.get(grandparentName); + } else if (ts.isPropertyAccessExpression(grandparentExpression)) { + grandparentName = grandparentExpression.name.escapedText.toString(); + parentComponent = collection.get(grandparentName); + } else { + // ignore + } + if (!parentComponent) { + parentComponent = new Set(); + } + return [grandparentName, ...parentComponent]; +} + +function processAnimateTo(node: ts.CallExpression): ts.CallExpression { + return ts.factory.updateCallExpression(node, ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(GLOBAL_CONTEXT), ts.factory.createIdentifier(ATTRIBUTE_ANIMATETO)), + node.typeArguments, node.arguments); +} + +function addUpdateParamsFunc(statements: ts.Statement[]): ts.MethodDeclaration { + return createParamsInitBlock(COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, statements); +} + +function addDeleteParamsFunc(statements: ts.PropertyDeclaration[]): ts.MethodDeclaration { + const deleteStatements: ts.ExpressionStatement[] = []; + statements.forEach((statement: ts.PropertyDeclaration) => { + const name: ts.Identifier = statement.name as ts.Identifier; + const paramsStatement: ts.ExpressionStatement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(`__${name.escapedText.toString()}`)), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_DELETE_PARAMS)), undefined, [])); + deleteStatements.push(paramsStatement); + }); + const defaultStatement: ts.ExpressionStatement = + ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_SUBSCRIBER_MANAGER), + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_GET_FUNCTION)), undefined, []), + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_DELETE_FUNCTION)), + undefined, [ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), ts.factory.createIdentifier(ABOUT_TO_BE_DELETE_FUNCTION_ID)), + undefined, [])])); + deleteStatements.push(defaultStatement); + const deleteParamsMethod: ts.MethodDeclaration = + createParamsInitBlock(COMPONENT_CONSTRUCTOR_DELETE_PARAMS, deleteStatements); + return deleteParamsMethod; +} + +function createParamsInitBlock(express: string, statements: ts.Statement[]): 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)); + return methodDeclaration; +} + +function validateBuildMethodCount(buildCount: BuildCount, parentComponentName: ts.Identifier, + log: LogInfo[]): void { + if (buildCount.count !== 1) { + log.push({ + type: LogType.ERROR, + message: `struct '${parentComponentName.getText()}' must be at least or at most one 'build' method.`, + pos: parentComponentName.getStart() + }); + } +} + +function validateInheritClass(node: ts.ClassDeclaration, log: LogInfo[]): void { + if (node.heritageClauses) { + log.push({ + type: LogType.ERROR, + message: '@Component should not be inherit other Classes.', + pos: node.heritageClauses.pos + }); + } +} + +function validateHasController(componentName: ts.Identifier, checkController: ControllerType, + log: LogInfo[]): void { + if (!checkController.hasController) { + log.push({ + type: LogType.ERROR, + message: '@CustomDialog component should have a property of the CustomDialogController type.', + pos: componentName.pos + }); + } +} diff --git a/compiler/src/process_component_constructor.ts b/compiler/src/process_component_constructor.ts index c963af058d8c16ba8ac9d120f09af38eb4b39ade..fa619f07aa524e3990840075a21f738f4732a5df 100644 --- a/compiler/src/process_component_constructor.ts +++ b/compiler/src/process_component_constructor.ts @@ -1,95 +1,95 @@ -/* - * 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 { - COMPONENT_CONSTRUCTOR_ID, - COMPONENT_CONSTRUCTOR_PARENT, - COMPONENT_CONSTRUCTOR_PARAMS, - COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, - COMPONENT_WATCH_FUNCTION -} from './pre_define'; - -export function getInitConstructor(members: ts.NodeArray): ts.ConstructorDeclaration { - let ctorNode: any = members.find(item => { - return ts.isConstructorDeclaration(item); - }); - if (ctorNode) { - ctorNode = updateConstructor(ctorNode, [], [], true); - } - return ctorNode; -} - -export function updateConstructor(ctorNode: ts.ConstructorDeclaration, - para: ts.ParameterDeclaration[], addStatements: ts.Statement[], - isSuper: boolean = false): ts.ConstructorDeclaration { - let modifyPara: ts.ParameterDeclaration[]; - if (para && para.length) { - modifyPara = Array.from(ctorNode.parameters); - if (modifyPara) { - modifyPara.push(...para); - } - } - let modifyBody: ts.Statement[]; - if (addStatements && addStatements.length && ctorNode) { - modifyBody = Array.from(ctorNode.body.statements); - if (modifyBody) { - if (isSuper) { - modifyBody.unshift(...addStatements); - } else { - modifyBody.push(...addStatements); - } - } - } - if (ctorNode) { - ctorNode = ts.factory.updateConstructorDeclaration(ctorNode, ctorNode.decorators, - ctorNode.modifiers, modifyPara || ctorNode.parameters, - ts.factory.createBlock(modifyBody || ctorNode.body.statements, true)); - } - return ctorNode; -} - -export function addConstructor(ctorNode: any, watchMap: Map) - : ts.ConstructorDeclaration { - const watchStatements: ts.ExpressionStatement[] = []; - watchMap.forEach((value, key) => { - const watchNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createThis(), - ts.factory.createIdentifier(COMPONENT_WATCH_FUNCTION) - ), - undefined, - [ - ts.factory.createStringLiteral(key), - ts.isStringLiteral(value) ? - ts.factory.createPropertyAccessExpression(ts.factory.createThis(), - ts.factory.createIdentifier(value.text)) : value as ts.PropertyAccessExpression - ] - )); - watchStatements.push(watchNode); - }); - const callSuperStatement: ts.Statement = ts.factory.createExpressionStatement( - ts.factory.createCallExpression(ts.factory.createSuper(), undefined, - [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_ID), - ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT)])); - const updateWithValueParamsStatement: ts.Statement = ts.factory.createExpressionStatement( - ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( - 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); -} +/* + * 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 { + COMPONENT_CONSTRUCTOR_ID, + COMPONENT_CONSTRUCTOR_PARENT, + COMPONENT_CONSTRUCTOR_PARAMS, + COMPONENT_CONSTRUCTOR_UPDATE_PARAMS, + COMPONENT_WATCH_FUNCTION +} from './pre_define'; + +export function getInitConstructor(members: ts.NodeArray): ts.ConstructorDeclaration { + let ctorNode: any = members.find(item => { + return ts.isConstructorDeclaration(item); + }); + if (ctorNode) { + ctorNode = updateConstructor(ctorNode, [], [], true); + } + return ctorNode; +} + +export function updateConstructor(ctorNode: ts.ConstructorDeclaration, + para: ts.ParameterDeclaration[], addStatements: ts.Statement[], + isSuper: boolean = false): ts.ConstructorDeclaration { + let modifyPara: ts.ParameterDeclaration[]; + if (para && para.length) { + modifyPara = Array.from(ctorNode.parameters); + if (modifyPara) { + modifyPara.push(...para); + } + } + let modifyBody: ts.Statement[]; + if (addStatements && addStatements.length && ctorNode) { + modifyBody = Array.from(ctorNode.body.statements); + if (modifyBody) { + if (isSuper) { + modifyBody.unshift(...addStatements); + } else { + modifyBody.push(...addStatements); + } + } + } + if (ctorNode) { + ctorNode = ts.factory.updateConstructorDeclaration(ctorNode, ctorNode.decorators, + ctorNode.modifiers, modifyPara || ctorNode.parameters, + ts.factory.createBlock(modifyBody || ctorNode.body.statements, true)); + } + return ctorNode; +} + +export function addConstructor(ctorNode: any, watchMap: Map) + : ts.ConstructorDeclaration { + const watchStatements: ts.ExpressionStatement[] = []; + watchMap.forEach((value, key) => { + const watchNode: ts.ExpressionStatement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createThis(), + ts.factory.createIdentifier(COMPONENT_WATCH_FUNCTION) + ), + undefined, + [ + ts.factory.createStringLiteral(key), + ts.isStringLiteral(value) ? + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(value.text)) : value as ts.PropertyAccessExpression + ] + )); + watchStatements.push(watchNode); + }); + const callSuperStatement: ts.Statement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createSuper(), undefined, + [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_ID), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT)])); + const updateWithValueParamsStatement: ts.Statement = ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + 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); +} diff --git a/compiler/src/process_component_member.ts b/compiler/src/process_component_member.ts index f21635926b881123a9b930fa70bf4341020ae44e..b3fffb467283891bb6a25f7d119be1c858806cc0 100644 --- a/compiler/src/process_component_member.ts +++ b/compiler/src/process_component_member.ts @@ -1,885 +1,899 @@ -/* - * 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 { - INNER_COMPONENT_MEMBER_DECORATORS, - COMPONENT_NON_DECORATOR, - COMPONENT_STATE_DECORATOR, - COMPONENT_PROP_DECORATOR, - COMPONENT_LINK_DECORATOR, - COMPONENT_STORAGE_PROP_DECORATOR, - COMPONENT_STORAGE_LINK_DECORATOR, - COMPONENT_PROVIDE_DECORATOR, - COMPONENT_CONSUME_DECORATOR, - COMPONENT_OBJECT_LINK_DECORATOR, - COMPONENT_WATCH_DECORATOR, - COMPONENT_OBSERVED_DECORATOR, - OBSERVED_PROPERTY_SIMPLE, - OBSERVED_PROPERTY_OBJECT, - SYNCHED_PROPERTY_SIMPLE_ONE_WAY, - SYNCHED_PROPERTY_SIMPLE_TWO_WAY, - SYNCHED_PROPERTY_OBJECT_TWO_WAY, - SYNCHED_PROPERTY_NESED_OBJECT, - CREATE_GET_METHOD, - CREATE_SET_METHOD, - CREATE_NEWVALUE_IDENTIFIER, - CREATE_CONSTRUCTOR_PARAMS, - ADD_PROVIDED_VAR, - INITIALIZE_CONSUME_FUNCTION, - APP_STORAGE, - APP_STORAGE_SET_AND_PROP, - APP_STORAGE_SET_AND_LINK, - APP_STORAGE_GET_OR_SET, - COMPONENT_CONSTRUCTOR_UNDEFINED, - SET_CONTROLLER_METHOD, - SET_CONTROLLER_CTR, - SET_CONTROLLER_CTR_TYPE, - JS_DIALOG, - CUSTOM_DIALOG_CONTROLLER_BUILDER, - BASE_COMPONENT_NAME, - COMPONENT_CREATE_FUNCTION -} from './pre_define'; -import { - forbiddenUseStateType, - BUILDIN_STYLE_NAMES -} from './component_map'; -import { - observedClassCollection, - enumCollection, - componentCollection, - classMethodCollection -} from './validate_ui_syntax'; -import { updateConstructor } from './process_component_constructor'; -import { - LogType, - LogInfo, - componentInfo, - createFunction -} from './utils'; - -export type ControllerType = { - hasController: boolean -} - -export const observedPropertyDecorators: Set = - new Set([COMPONENT_STATE_DECORATOR, COMPONENT_PROVIDE_DECORATOR]); - -export const propAndLinkDecorators: Set = - new Set([COMPONENT_PROP_DECORATOR, COMPONENT_LINK_DECORATOR]); - -export const appStorageDecorators: Set = - new Set([COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_STORAGE_LINK_DECORATOR]); - -export const mandatorySpecifyDefaultValueDecorators: Set = - new Set([...observedPropertyDecorators, ...appStorageDecorators]); - -export const forbiddenSpecifyDefaultValueDecorators: Set = - new Set([...propAndLinkDecorators, COMPONENT_CONSUME_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]); - -export const mandatoryToInitViaParamDecorators: Set = - new Set([...propAndLinkDecorators, COMPONENT_OBJECT_LINK_DECORATOR]); - -export const setUpdateParamsDecorators: Set = - new Set([...observedPropertyDecorators, COMPONENT_PROP_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]); - -export const immutableDecorators: Set = - new Set([COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]); - -export const simpleTypes: Set = new Set([ts.SyntaxKind.StringKeyword, - ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.EnumDeclaration]); - -export const decoratorParamSet: Set = new Set(); - -export const stateObjectCollection: Set = new Set(); - -export class UpdateResult { - private itemUpdate: boolean = false; - private ctorUpdate: boolean = false; - private properity: ts.PropertyDeclaration; - private ctor: ts.ConstructorDeclaration; - private variableGet: ts.GetAccessorDeclaration; - private variableSet: ts.SetAccessorDeclaration; - private updateParams: ts.Statement; - private deleteParams: boolean = false; - private controllerSet: ts.MethodDeclaration; - - public setProperity(updateItem: ts.PropertyDeclaration) { - this.itemUpdate = true; - this.properity = updateItem; - } - - public setCtor(updateCtor: ts.ConstructorDeclaration) { - this.ctorUpdate = true; - this.ctor = updateCtor; - } - - public setControllerSet(updateControllerSet: ts.MethodDeclaration) { - this.controllerSet = updateControllerSet; - } - - public getControllerSet(): ts.MethodDeclaration { - return this.controllerSet; - } - - public setVariableGet(updateVariableGet: ts.GetAccessorDeclaration) { - this.variableGet = updateVariableGet; - } - - public setVariableSet(updateVariableSet: ts.SetAccessorDeclaration) { - this.variableSet = updateVariableSet; - } - - public setUpdateParams(updateParams: ts.Statement) { - this.updateParams = updateParams; - } - - public setDeleteParams(deleteParams: boolean) { - this.deleteParams = deleteParams; - } - - public isItemUpdate(): boolean { - return this.itemUpdate; - } - - public isCtorUpdate(): boolean { - return this.ctorUpdate; - } - - public getProperity(): ts.PropertyDeclaration { - return this.properity; - } - - public getCtor(): ts.ConstructorDeclaration { - return this.ctor; - } - - public getUpdateParams(): ts.Statement { - return this.updateParams; - } - - public getVariableGet(): ts.GetAccessorDeclaration { - return this.variableGet; - } - - public getVariableSet(): ts.SetAccessorDeclaration { - return this.variableSet; - } - - public isDeleteParams(): boolean { - return this.deleteParams; - } -} - -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): UpdateResult { - const updateResult: UpdateResult = new UpdateResult(); - const name: ts.Identifier = item.name as ts.Identifier; - if (!item.decorators || !item.decorators.length) { - curPropMap.set(name.escapedText.toString(), COMPONENT_NON_DECORATOR); - updateResult.setProperity(undefined); - updateResult.setUpdateParams(createUpdateParams(name, COMPONENT_NON_DECORATOR)); - updateResult.setCtor(updateConstructor(ctorNode, [], [ - createVariableInitStatement(item, COMPONENT_NON_DECORATOR, log, program)])); - 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); - } - return updateResult; -} - -function createControllerSet(node: ts.PropertyDeclaration, componentName: ts.Identifier, - name: ts.Identifier, checkController: ControllerType): ts.MethodDeclaration { - if (componentCollection.customDialogs.has(componentName.getText()) && node.type && - node.type.getText() === SET_CONTROLLER_CTR_TYPE) { - checkController.hasController = true; - return ts.factory.createMethodDeclaration(undefined, undefined, undefined, - ts.factory.createIdentifier(SET_CONTROLLER_METHOD), undefined, undefined, - [ts.factory.createParameterDeclaration(undefined, undefined, undefined, - ts.factory.createIdentifier(SET_CONTROLLER_CTR), undefined, - ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(SET_CONTROLLER_CTR_TYPE), - undefined), undefined)], undefined, ts.factory.createBlock( - [ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( - ts.factory.createPropertyAccessExpression(ts.factory.createThis(), name), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - ts.factory.createIdentifier(SET_CONTROLLER_CTR)))], true)); - } -} - -function processPropertyNodeDecorator(parentName: ts.Identifier, node: ts.PropertyDeclaration, - updateResult: UpdateResult, ctorNode: ts.ConstructorDeclaration, name: ts.Identifier, - watchMap: Map, log: LogInfo[], program: ts.Program): void { - let stateManagementDecoratorCount: number = 0; - for (let i = 0; i < node.decorators.length; i++) { - const decoratorName: string = node.decorators[i].getText().replace(/\(.*\)$/, '').trim(); - if (decoratorName !== COMPONENT_WATCH_DECORATOR) { - curPropMap.set(name.escapedText.toString(), decoratorName); - } - if (BUILDIN_STYLE_NAMES.has(decoratorName.replace('@', ''))) { - validateDuplicateDecorator(node.decorators[i], log); - } - if (decoratorName !== COMPONENT_WATCH_DECORATOR && isForbiddenUseStateType(node.type)) { - // @ts-ignore - validateForbiddenUseStateType(name, decoratorName, node.type.typeName.getText(), log); - return; - } - if (parentName.getText() === componentCollection.entryComponent && - mandatoryToInitViaParamDecorators.has(decoratorName)) { - validateHasIllegalDecoratorInEntry(parentName, name, decoratorName, log); - } - if (node.initializer && forbiddenSpecifyDefaultValueDecorators.has(decoratorName)) { - validatePropertyDefaultValue(name, decoratorName, log); - return; - } else if (!node.initializer && mandatorySpecifyDefaultValueDecorators.has(decoratorName)) { - validatePropertyNonDefaultValue(name, decoratorName, log); - return; - } - if (node.questionToken && mandatoryToInitViaParamDecorators.has(decoratorName)) { - validateHasIllegalQuestionToken(name, decoratorName, log); - } - if (!isSimpleType(node.type, program)) { - stateObjectCollection.add(name.escapedText.toString()); - } - if (decoratorName === COMPONENT_WATCH_DECORATOR && - validateWatchDecorator(name, node.decorators.length, log)) { - processWatch(node, node.decorators[i], watchMap, log); - } else if (INNER_COMPONENT_MEMBER_DECORATORS.has(decoratorName)) { - stateManagementDecoratorCount += 1; - processStateDecorators(node, decoratorName, updateResult, ctorNode, log, program); - } - } - if (stateManagementDecoratorCount > 1) { - validateMultiDecorators(name, log); - return; - } -} - -function processStateDecorators(node: ts.PropertyDeclaration, decorator: string, - updateResult: UpdateResult, ctorNode: ts.ConstructorDeclaration, log: LogInfo[], - program: ts.Program): 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); - if (variableInitStatement) { - updateState.push(variableInitStatement); - } - addAddProvidedVar(node, name, decorator, updateState); - updateResult.setCtor(updateConstructor(ctorNode, [], [...updateState], false)); - updateResult.setVariableGet(createGetAccessor(name, CREATE_GET_METHOD)); - if (!immutableDecorators.has(decorator)) { - updateResult.setVariableSet(createSetAccessor(name, CREATE_SET_METHOD)); - } - if (setUpdateParamsDecorators.has(decorator)) { - updateResult.setUpdateParams(createUpdateParams(name, decorator)); - } - updateResult.setDeleteParams(true); -} - -function processWatch(node: ts.PropertyDeclaration, decorator: ts.Decorator, - watchMap: Map, log: LogInfo[]): void { - if (node.name) { - const propertyName: string = node.name.getText(); - if (decorator.expression && ts.isCallExpression(decorator.expression) && - decorator.expression.arguments && decorator.expression.arguments.length === 1) { - const currentClassMethod: Set = classMethodCollection.get(node.parent.name.getText()); - const argument: ts.Node = decorator.expression.arguments[0]; - if (ts.isStringLiteral(argument)) { - if (currentClassMethod.has(argument.text)) { - watchMap.set(propertyName, argument); - } else { - log.push({ - type: LogType.ERROR, - message: `Cannot find name ${argument.getText()} in struct '${node.parent.name.getText()}'.`, - pos: argument.getStart() - }); - } - } else if (ts.isIdentifier(decorator.expression.arguments[0])) { - const content: string = decorator.expression.arguments[0].getText(); - const propertyNode: ts.PropertyAccessExpression = createPropertyAccessExpressionWithThis(content); - watchMap.set(propertyName, propertyNode); - decoratorParamSet.add(content); - validateWatchParam(LogType.WARN, argument.getStart(), log); - } else if (ts.isPropertyAccessExpression(decorator.expression.arguments[0])) { - watchMap.set(propertyName, decorator.expression.arguments[0]); - validateWatchParam(LogType.WARN, argument.getStart(), log); - } else { - validateWatchParam(LogType.ERROR, argument.getStart(), log); - } - } - } -} - -function createVariableInitStatement(node: ts.PropertyDeclaration, decorator: string, - log: LogInfo[], program: ts.Program): ts.Statement { - const name: ts.Identifier = node.name as ts.Identifier; - let type: ts.TypeNode; - let updateState: ts.ExpressionStatement; - if (node.type) { - type = node.type; - } - switch (decorator) { - case COMPONENT_NON_DECORATOR: - updateState = updateNormalProperty(node, name, log); - break; - case COMPONENT_STATE_DECORATOR: - case COMPONENT_PROVIDE_DECORATOR: - updateState = updateObservedProperty(node, name, type, program); - break; - case COMPONENT_LINK_DECORATOR: - updateState = updateSynchedPropertyTwoWay(name, type, program); - break; - case COMPONENT_PROP_DECORATOR: - updateState = updateSynchedPropertyOneWay(name, type, decorator, log, program); - break; - case COMPONENT_STORAGE_PROP_DECORATOR: - case COMPONENT_STORAGE_LINK_DECORATOR: - const setFuncName: string = decorator === COMPONENT_STORAGE_PROP_DECORATOR ? - APP_STORAGE_SET_AND_PROP : APP_STORAGE_SET_AND_LINK; - updateState = updateStoragePropAndLinkProperty(node, name, setFuncName, log); - break; - case COMPONENT_OBJECT_LINK_DECORATOR: - updateState = updateSynchedPropertyNesedObject(name, type, decorator, log); - break; - case COMPONENT_CONSUME_DECORATOR: - updateState = updateConsumeProperty(node, name); - break; - } - return updateState; -} - -function createUpdateParams(name: ts.Identifier, decorator: string): ts.Statement { - let updateParamsNode: ts.Statement; - switch (decorator) { - case COMPONENT_NON_DECORATOR: - case COMPONENT_STATE_DECORATOR: - case COMPONENT_PROVIDE_DECORATOR: - updateParamsNode = createUpdateParamsWithIf(name); - break; - case COMPONENT_PROP_DECORATOR: - updateParamsNode = createUpdateParamsWithoutIf(name); - break; - case COMPONENT_OBJECT_LINK_DECORATOR: - updateParamsNode = createUpdateParamsWithSet(name); - break; - } - return updateParamsNode; -} - -function createUpdateParamsWithIf(name: ts.Identifier): ts.IfStatement { - return ts.factory.createIfStatement(ts.factory.createBinaryExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), - ts.factory.createIdentifier(name.escapedText.toString())), - ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), - ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED)), ts.factory.createBlock([ - createUpdateParamsWithoutIf(name)], true), undefined); -} - -function createUpdateParamsWithoutIf(name: ts.Identifier): ts.ExpressionStatement { - return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( - createPropertyAccessExpressionWithThis(name.getText()), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), - createPropertyAccessExpressionWithParams(name.getText()))); -} - -function createUpdateParamsWithSet(name: ts.Identifier): ts.ExpressionStatement { - return ts.factory.createExpressionStatement(ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(createPropertyAccessExpressionWithThis(`__${name.getText()}`), - ts.factory.createIdentifier(CREATE_SET_METHOD)), undefined, - [createPropertyAccessExpressionWithParams(name.getText())])); -} - -function updateNormalProperty(node: ts.PropertyDeclaration, name: ts.Identifier, - log: LogInfo[]): ts.ExpressionStatement { - const init: ts.Expression = processCustomDialogController(node, log); - return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( - createPropertyAccessExpressionWithThis(name.getText()), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), init || - ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED))); -} - -function processCustomDialogController(node: ts.PropertyDeclaration, - log: LogInfo[]): ts.Expression { - if (node.initializer && ts.isNewExpression(node.initializer) && - ts.isIdentifier(node.initializer.expression) && - node.initializer.expression.getText() === SET_CONTROLLER_CTR_TYPE) { - return createCustomDialogController(node, node.initializer, log); - } - return node.initializer; -} - -function createCustomDialogController(parent: ts.PropertyDeclaration, node: ts.NewExpression, - log: LogInfo[]): ts.NewExpression { - if (node.arguments && node.arguments.length === 1 && - ts.isObjectLiteralExpression(node.arguments[0]) && node.arguments[0].properties) { - const newproperties: ts.ObjectLiteralElementLike[] = node.arguments[0].properties.map((item) => { - if (isCustomDialogControllerPropertyAssignment(item, log)) { - item = processCustomDialogControllerPropertyAssignment(parent, item as ts.PropertyAssignment); - } - return item; - }); - return ts.factory.createNewExpression(node.expression, node.typeArguments, - [ts.factory.createObjectLiteralExpression(newproperties, true), ts.factory.createThis()]); - } -} - -function isCustomDialogControllerPropertyAssignment(node: ts.ObjectLiteralElementLike, - log: LogInfo[]): boolean { - if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && - node.name.getText() === CUSTOM_DIALOG_CONTROLLER_BUILDER) { - if (ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && - componentCollection.customDialogs.has(node.initializer.expression.getText())) { - return true; - } else { - validateCustomDialogControllerBuilderInit(node, log); - } - } -} - -function processCustomDialogControllerPropertyAssignment(parent: ts.PropertyDeclaration, - node: ts.PropertyAssignment): ts.PropertyAssignment { - if (ts.isCallExpression(node.initializer)) { - return ts.factory.updatePropertyAssignment(node, node.name, - processCustomDialogControllerBuilder(parent, node.initializer)); - } -} - -function processCustomDialogControllerBuilder(parent: ts.PropertyDeclaration, - node: ts.CallExpression): ts.ArrowFunction { - const newExp: ts.Expression = createCustomComponentNewExpression(node); - const jsDialog: ts.Identifier = ts.factory.createIdentifier(JS_DIALOG); - return createCustomComponentBuilderArrowFunction(parent, jsDialog, newExp); -} - -function updateObservedProperty(item: ts.PropertyDeclaration, name: ts.Identifier, - type: ts.TypeNode, program: ts.Program): ts.ExpressionStatement { - return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( - createPropertyAccessExpressionWithThis(`__${name.getText()}`), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createNewExpression( - ts.factory.createIdentifier(isSimpleType(type, program) ? OBSERVED_PROPERTY_SIMPLE : - OBSERVED_PROPERTY_OBJECT), undefined, [item.initializer, ts.factory.createThis(), - ts.factory.createStringLiteral(name.escapedText.toString())]))); -} - -function updateSynchedPropertyTwoWay(nameIdentifier: ts.Identifier, type: ts.TypeNode, - program: ts.Program): ts.ExpressionStatement { - const name: string = nameIdentifier.escapedText.toString(); - const functionName: string = isSimpleType(type, program) ? - SYNCHED_PROPERTY_SIMPLE_TWO_WAY : SYNCHED_PROPERTY_OBJECT_TWO_WAY; - return createInitExpressionStatementForDecorator(name, functionName, - createPropertyAccessExpressionWithParams(name)); -} - -function updateSynchedPropertyOneWay(nameIdentifier: ts.Identifier, type: ts.TypeNode, - decoractor: string, log: LogInfo[], program: ts.Program): ts.ExpressionStatement { - const name: string = nameIdentifier.escapedText.toString(); - if (isSimpleType(type, program)) { - return createInitExpressionStatementForDecorator(name, SYNCHED_PROPERTY_SIMPLE_ONE_WAY, - createPropertyAccessExpressionWithParams(name)); - } else { - validateNonSimpleType(nameIdentifier, decoractor, log); - } -} - -function updateStoragePropAndLinkProperty(node: ts.PropertyDeclaration, name: ts.Identifier, - setFuncName: string, log: LogInfo[]): ts.ExpressionStatement { - if (isSingleKey(node)) { - const key: string = getDecoratorKey(node); - return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( - createPropertyAccessExpressionWithThis(`__${name.getText()}`), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(APP_STORAGE), - ts.factory.createIdentifier(APP_STORAGE_GET_OR_SET)), undefined, []), - ts.factory.createIdentifier(setFuncName)), undefined, [ts.factory.createStringLiteral(key), - node.initializer, ts.factory.createThis()]))); - } else { - validateAppStorageDecoractorsNonSingleKey(node, log); - } -} - -function getDecoratorKey(node: ts.PropertyDeclaration): string { - let key: string; - // @ts-ignore - const keyNameNode: ts.Node = node.decorators[0].expression.arguments[0]; - if (ts.isIdentifier(keyNameNode)) { - key = keyNameNode.getText(); - decoratorParamSet.add(key); - } else if (ts.isStringLiteral(keyNameNode)) { - key = keyNameNode.text; - } - return key; -} - -function updateSynchedPropertyNesedObject(nameIdentifier: ts.Identifier, - type: ts.TypeNode, decoractor: string, log: LogInfo[]): ts.ExpressionStatement { - if (isObservedClassType(type)) { - return createInitExpressionStatementForDecorator(nameIdentifier.getText(), SYNCHED_PROPERTY_NESED_OBJECT, - createPropertyAccessExpressionWithParams(nameIdentifier.getText())); - } else { - validateNonObservedClassType(nameIdentifier, decoractor, log); - } -} - -function updateConsumeProperty(node: ts.PropertyDeclaration, - nameIdentifier: ts.Identifier): ts.ExpressionStatement { - const name: string = nameIdentifier.getText(); - let propertyOrAliasName: string; - if (isSingleKey(node)) { - propertyOrAliasName = getDecoratorKey(node); - } else { - propertyOrAliasName = name; - } - return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( - createPropertyAccessExpressionWithThis(`__${name}`), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createCallExpression( - createPropertyAccessExpressionWithThis(INITIALIZE_CONSUME_FUNCTION), undefined, [ - ts.factory.createStringLiteral(propertyOrAliasName), ts.factory.createStringLiteral(name)]))); -} - -function createCustomComponentBuilderArrowFunction(parent: ts.PropertyDeclaration, - jsDialog: ts.Identifier, newExp: ts.Expression): ts.ArrowFunction { - return ts.factory.createArrowFunction(undefined, undefined, [], undefined, - ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createBlock([ - ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList( - [ts.factory.createVariableDeclaration(jsDialog, undefined, undefined, newExp)], - ts.NodeFlags.Let)), ts.factory.createExpressionStatement(ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression(jsDialog, - ts.factory.createIdentifier(SET_CONTROLLER_METHOD)), undefined, - [ts.factory.createPropertyAccessExpression(ts.factory.createThis(), - parent.name as ts.Identifier)])), ts.factory.createExpressionStatement( - createViewCreate(jsDialog))], true)); -} - -export function createViewCreate(node: ts.NewExpression | ts.Identifier): ts.CallExpression { - return createFunction(ts.factory.createIdentifier(BASE_COMPONENT_NAME), - ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), ts.factory.createNodeArray([node])); -} - -export function createCustomComponentNewExpression(node: ts.CallExpression): ts.NewExpression { - const newNode: ts.NewExpression = ts.factory.createNewExpression(node.expression, - node.typeArguments, node.arguments.length ? node.arguments : []); - return addCustomComponentId(newNode); -} - -function addCustomComponentId(node: ts.NewExpression): ts.NewExpression { - for (const item of componentCollection.customComponents) { - componentInfo.componentNames.add(item); - } - componentInfo.componentNames.forEach((name: string) => { - const nodeIdentifier: ts.Identifier | ts.PropertyAccessExpression = - node.expression as ts.Identifier | ts.PropertyAccessExpression; - let argumentsArray: ts.Expression[]; - if (node.arguments && node.arguments.length) { - argumentsArray = Array.from(node.arguments); - } - if (nodeIdentifier && (ts.isIdentifier(nodeIdentifier) && - nodeIdentifier.escapedText === name || ts.isPropertyAccessExpression(nodeIdentifier) && - ts.isIdentifier(nodeIdentifier.name) && nodeIdentifier.name.escapedText === name)) { - if (!argumentsArray) { - argumentsArray = [ts.factory.createObjectLiteralExpression([], true)]; - } - argumentsArray.unshift(ts.factory.createStringLiteral((++componentInfo.id).toString()), - ts.factory.createThis()); - node = - ts.factory.updateNewExpression(node, node.expression, node.typeArguments, argumentsArray); - } else if (argumentsArray) { - node = - ts.factory.updateNewExpression(node, node.expression, node.typeArguments, argumentsArray); - } - }); - return node; -} - -function createInitExpressionStatementForDecorator(propertyName: string, functionName: string, - parameterNode: ts.Expression): ts.ExpressionStatement { - return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( - createPropertyAccessExpressionWithThis(`__${propertyName}`), - ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createNewExpression( - ts.factory.createIdentifier(functionName), undefined, [parameterNode, ts.factory.createThis(), - ts.factory.createStringLiteral(propertyName)]))); -} - -function createPropertyAccessExpressionWithParams(propertyName: string): ts.PropertyAccessExpression { - return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), - ts.factory.createIdentifier(propertyName)); -} - -function createPropertyAccessExpressionWithThis(propertyName: string): ts.PropertyAccessExpression { - return ts.factory.createPropertyAccessExpression(ts.factory.createThis(), - ts.factory.createIdentifier(propertyName)); -} - -function addAddProvidedVar(node: ts.PropertyDeclaration, name: ts.Identifier, - decoratorName: string, updateState: ts.Statement[]): void { - if (decoratorName === COMPONENT_PROVIDE_DECORATOR) { - if (isSingleKey(node)) { - updateState.push(createAddProvidedVar(getDecoratorKey(node), name)); - } - updateState.push(createAddProvidedVar(name.getText(), name)); - } -} - -function createAddProvidedVar(propertyOrAliasName: string, - name: ts.Identifier): ts.ExpressionStatement { - return ts.factory.createExpressionStatement(ts.factory.createCallExpression( - createPropertyAccessExpressionWithThis(ADD_PROVIDED_VAR), undefined, [ - ts.factory.createStringLiteral(propertyOrAliasName), - createPropertyAccessExpressionWithThis(`__${name.getText()}`)])); -} - -function createGetAccessor(item: ts.Identifier, express: string): ts.GetAccessorDeclaration { - const getAccessorStatement: ts.GetAccessorDeclaration = - ts.factory.createGetAccessorDeclaration(undefined, undefined, item, [], undefined, - ts.factory.createBlock([ts.factory.createReturnStatement( - ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( - createPropertyAccessExpressionWithThis(`__${item.getText()}`), - ts.factory.createIdentifier(express)), undefined, []))], true)); - return getAccessorStatement; -} - -function createSetAccessor(item: ts.Identifier, express: string): 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, - undefined)], ts.factory.createBlock([ts.factory.createExpressionStatement( - ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( - createPropertyAccessExpressionWithThis(`__${item.getText()}`), - ts.factory.createIdentifier(express)), undefined, - [ts.factory.createIdentifier(CREATE_NEWVALUE_IDENTIFIER)]))], true)); - return setAccessorStatement; -} - -function isForbiddenUseStateType(typeNode: ts.TypeNode): boolean { - if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName) && - forbiddenUseStateType.has(typeNode.typeName.getText())) { - return true; - } - return false; -} - -function isSimpleType(typeNode: ts.TypeNode, program: ts.Program): boolean { - let checker: ts.TypeChecker; - if (program) { - checker = program.getTypeChecker(); - } - const enumType: ts.SyntaxKind = getEnumType(typeNode, checker); - if (simpleTypes.has(enumType || typeNode.kind) || isEnumtype(typeNode)) { - return true; - } else if (ts.isUnionTypeNode(typeNode) && typeNode.types) { - const types: ts.NodeArray = typeNode.types; - for (let i = 0; i < types.length; i++) { - const enumType: ts.SyntaxKind = getEnumType(types[i], checker); - if (!simpleTypes.has(enumType || types[i].kind) && !isEnumtype(typeNode)) { - return false; - } - } - return true; - } - return false; -} - -function getEnumType(typeNode: ts.TypeNode, checker: ts.TypeChecker): ts.SyntaxKind { - if (!checker) { - return; - } - if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) { - const type: ts.Type = - checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(typeNode.typeName)); - if (type.symbol && type.symbol.valueDeclaration) { - return type.symbol.valueDeclaration.kind; - } - } -} - -function isEnumtype(typeNode: ts.TypeNode): boolean { - if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) { - return enumCollection.has(typeNode.typeName.getText()); - } -} - -function isObservedClassType(type: ts.TypeNode): boolean { - if (ts.isTypeReferenceNode(type) && observedClassCollection.has(type.getText())) { - return true; - } else if (ts.isUnionTypeNode(type) && type.types) { - const types: ts.NodeArray = type.types; - for (let i = 0; i < types.length; i++) { - if (!observedClassCollection.has(types[i].getText())) { - return false; - } - } - return true; - } - return false; -} - -function validateAppStorageDecoractorsNonSingleKey(node: ts.PropertyDeclaration, - log: LogInfo[]): void { - if (ts.isIdentifier(node.decorators[0].expression)) { - validateDecoratorNonSingleKey(node.decorators[0].expression, log); - } else if (ts.isCallExpression(node.decorators[0].expression) && - ts.isIdentifier(node.decorators[0].expression.expression)) { - validateDecoratorNonSingleKey(node.decorators[0].expression.expression, log); - } -} - -function isSingleKey(node: ts.PropertyDeclaration): boolean { - if (ts.isCallExpression(node.decorators[0].expression) && - node.decorators[0].expression.arguments && - node.decorators[0].expression.arguments.length === 1 && - (ts.isIdentifier(node.decorators[0].expression.arguments[0]) || - ts.isStringLiteral(node.decorators[0].expression.arguments[0]))) { - return true; - } -} - -function validateMultiDecorators(name: ts.Identifier, log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: `The property '${name.escapedText.toString()}' cannot have mutilate state management decorators.`, - pos: name.getStart() - }); -} - -function validateDecoratorNonSingleKey(decoratorsIdentifier: ts.Identifier, - log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: `The decorator ${decoratorsIdentifier.escapedText.toString()} should have a single key.`, - pos: decoratorsIdentifier.getStart() - }); -} - -function validatePropertyNonDefaultValue(propertyName: ts.Identifier, decorator: string, - log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: `The ${decorator} property '${propertyName.getText()}' must be specified a default value.`, - pos: propertyName.getStart() - }); -} - -function validatePropertyDefaultValue(propertyName: ts.Identifier, decorator: string, - log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: `The ${decorator} property '${propertyName.getText()}' cannot be specified a default value.`, - pos: propertyName.getStart() - }); -} - -function validatePropertyNonType(propertyName: ts.Identifier, log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: `The property '${propertyName.getText()}' must specify a type.`, - pos: propertyName.getStart() - }); -} - -function validateNonSimpleType(propertyName: ts.Identifier, decorator: string, - log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: `The type of the ${decorator} property '${propertyName.getText()}' ` + - `can only be string, number or boolean.`, - pos: propertyName.getStart() - }); -} - -function validateNonObservedClassType(propertyName: ts.Identifier, decorator: string, - log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: `The type of the ${decorator} property '${propertyName.getText()}' can only be ` + - `objects of classes decorated with ${COMPONENT_OBSERVED_DECORATOR} class decorator in ets (not ts).`, - pos: propertyName.getStart() - }); -} - -function validateHasIllegalQuestionToken(propertyName: ts.Identifier, decorator: string, - log: LogInfo[]): void { - log.push({ - type: LogType.WARN, - message: `The ${decorator} property '${propertyName.getText()}' cannot have a question token.`, - pos: propertyName.getStart() - }); -} - -function validateHasIllegalDecoratorInEntry(parentName: ts.Identifier, propertyName: ts.Identifier, - decorator: string, log: LogInfo[]): void { - log.push({ - type: LogType.WARN, - message: `The @Entry component '${parentName.getText()}' cannot have the ` + - `${decorator} property '${propertyName.getText()}'.`, - pos: propertyName.getStart() - }); -} - -function validateForbiddenUseStateType(propertyName: ts.Identifier, decorator: string, type: string, - log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: `The ${decorator} property '${propertyName.getText()}' cannot be a '${type}' object.`, - pos: propertyName.getStart() - }); -} - -function validateDuplicateDecorator(decorator: ts.Decorator, log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: `The decorator '${decorator.getText()}' cannot have the same name as the build-in ` + - `style attribute '${decorator.getText().replace('@', '')}'.`, - pos: decorator.getStart() - }); -} - -function validateWatchDecorator(propertyName: ts.Identifier, length: number, log: LogInfo[]): boolean { - if (length === 1) { - log.push({ - type: LogType.ERROR, - message: `Regular variable '${propertyName.escapedText.toString()}' can not be decorated with @Watch.`, - pos: propertyName.getStart() - }); - return false; - } - return true; -} - -function validateWatchParam(type: LogType, pos: number, log: LogInfo[]): void { - log.push({ - type: type, - message: 'The parameter should be a string.', - pos: pos - }); -} - -function validateCustomDialogControllerBuilderInit(node: ts.ObjectLiteralElementLike, - log: LogInfo[]): void { - log.push({ - type: LogType.ERROR, - message: 'The builder should be initialized with a @CustomDialog Component.', - pos: node.getStart() - }); -} +/* + * 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 { + INNER_COMPONENT_MEMBER_DECORATORS, + COMPONENT_NON_DECORATOR, + COMPONENT_STATE_DECORATOR, + COMPONENT_PROP_DECORATOR, + COMPONENT_LINK_DECORATOR, + COMPONENT_STORAGE_PROP_DECORATOR, + COMPONENT_STORAGE_LINK_DECORATOR, + COMPONENT_PROVIDE_DECORATOR, + COMPONENT_CONSUME_DECORATOR, + COMPONENT_OBJECT_LINK_DECORATOR, + COMPONENT_WATCH_DECORATOR, + COMPONENT_OBSERVED_DECORATOR, + OBSERVED_PROPERTY_SIMPLE, + OBSERVED_PROPERTY_OBJECT, + SYNCHED_PROPERTY_SIMPLE_ONE_WAY, + SYNCHED_PROPERTY_SIMPLE_TWO_WAY, + SYNCHED_PROPERTY_OBJECT_TWO_WAY, + SYNCHED_PROPERTY_NESED_OBJECT, + CREATE_GET_METHOD, + CREATE_SET_METHOD, + CREATE_NEWVALUE_IDENTIFIER, + CREATE_CONSTRUCTOR_PARAMS, + ADD_PROVIDED_VAR, + INITIALIZE_CONSUME_FUNCTION, + APP_STORAGE, + APP_STORAGE_SET_AND_PROP, + APP_STORAGE_SET_AND_LINK, + APP_STORAGE_GET_OR_SET, + COMPONENT_CONSTRUCTOR_UNDEFINED, + SET_CONTROLLER_METHOD, + SET_CONTROLLER_CTR, + SET_CONTROLLER_CTR_TYPE, + JS_DIALOG, + CUSTOM_DIALOG_CONTROLLER_BUILDER, + BASE_COMPONENT_NAME, + COMPONENT_CREATE_FUNCTION +} from './pre_define'; +import { + forbiddenUseStateType, + BUILDIN_STYLE_NAMES +} from './component_map'; +import { + observedClassCollection, + enumCollection, + componentCollection, + classMethodCollection +} from './validate_ui_syntax'; +import { updateConstructor } from './process_component_constructor'; +import { + LogType, + LogInfo, + componentInfo, + createFunction +} from './utils'; +import { + createReference, + isProperty +} from './process_component_class'; + +export type ControllerType = { + hasController: boolean +} + +export const observedPropertyDecorators: Set = + new Set([COMPONENT_STATE_DECORATOR, COMPONENT_PROVIDE_DECORATOR]); + +export const propAndLinkDecorators: Set = + new Set([COMPONENT_PROP_DECORATOR, COMPONENT_LINK_DECORATOR]); + +export const appStorageDecorators: Set = + new Set([COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_STORAGE_LINK_DECORATOR]); + +export const mandatorySpecifyDefaultValueDecorators: Set = + new Set([...observedPropertyDecorators, ...appStorageDecorators]); + +export const forbiddenSpecifyDefaultValueDecorators: Set = + new Set([...propAndLinkDecorators, COMPONENT_CONSUME_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]); + +export const mandatoryToInitViaParamDecorators: Set = + new Set([...propAndLinkDecorators, COMPONENT_OBJECT_LINK_DECORATOR]); + +export const setUpdateParamsDecorators: Set = + new Set([...observedPropertyDecorators, COMPONENT_PROP_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]); + +export const immutableDecorators: Set = + new Set([COMPONENT_STORAGE_PROP_DECORATOR, COMPONENT_OBJECT_LINK_DECORATOR]); + +export const simpleTypes: Set = new Set([ts.SyntaxKind.StringKeyword, + ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.EnumDeclaration]); + +export const decoratorParamSet: Set = new Set(); + +export const stateObjectCollection: Set = new Set(); + +export class UpdateResult { + private itemUpdate: boolean = false; + private ctorUpdate: boolean = false; + private properity: ts.PropertyDeclaration; + private ctor: ts.ConstructorDeclaration; + private variableGet: ts.GetAccessorDeclaration; + private variableSet: ts.SetAccessorDeclaration; + private updateParams: ts.Statement; + private deleteParams: boolean = false; + private controllerSet: ts.MethodDeclaration; + + public setProperity(updateItem: ts.PropertyDeclaration) { + this.itemUpdate = true; + this.properity = updateItem; + } + + public setCtor(updateCtor: ts.ConstructorDeclaration) { + this.ctorUpdate = true; + this.ctor = updateCtor; + } + + public setControllerSet(updateControllerSet: ts.MethodDeclaration) { + this.controllerSet = updateControllerSet; + } + + public getControllerSet(): ts.MethodDeclaration { + return this.controllerSet; + } + + public setVariableGet(updateVariableGet: ts.GetAccessorDeclaration) { + this.variableGet = updateVariableGet; + } + + public setVariableSet(updateVariableSet: ts.SetAccessorDeclaration) { + this.variableSet = updateVariableSet; + } + + public setUpdateParams(updateParams: ts.Statement) { + this.updateParams = updateParams; + } + + public setDeleteParams(deleteParams: boolean) { + this.deleteParams = deleteParams; + } + + public isItemUpdate(): boolean { + return this.itemUpdate; + } + + public isCtorUpdate(): boolean { + return this.ctorUpdate; + } + + public getProperity(): ts.PropertyDeclaration { + return this.properity; + } + + public getCtor(): ts.ConstructorDeclaration { + return this.ctor; + } + + public getUpdateParams(): ts.Statement { + return this.updateParams; + } + + public getVariableGet(): ts.GetAccessorDeclaration { + return this.variableGet; + } + + public getVariableSet(): ts.SetAccessorDeclaration { + return this.variableSet; + } + + public isDeleteParams(): boolean { + return this.deleteParams; + } +} + +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): UpdateResult { + const updateResult: UpdateResult = new UpdateResult(); + const name: ts.Identifier = item.name as ts.Identifier; + if (!item.decorators || !item.decorators.length) { + curPropMap.set(name.escapedText.toString(), COMPONENT_NON_DECORATOR); + updateResult.setProperity(undefined); + updateResult.setUpdateParams(createUpdateParams(name, COMPONENT_NON_DECORATOR)); + updateResult.setCtor(updateConstructor(ctorNode, [], [ + createVariableInitStatement(item, COMPONENT_NON_DECORATOR, log, program, context)])); + 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); + } + return updateResult; +} + +function createControllerSet(node: ts.PropertyDeclaration, componentName: ts.Identifier, + name: ts.Identifier, checkController: ControllerType): ts.MethodDeclaration { + if (componentCollection.customDialogs.has(componentName.getText()) && node.type && + node.type.getText() === SET_CONTROLLER_CTR_TYPE) { + checkController.hasController = true; + return ts.factory.createMethodDeclaration(undefined, undefined, undefined, + ts.factory.createIdentifier(SET_CONTROLLER_METHOD), undefined, undefined, + [ts.factory.createParameterDeclaration(undefined, undefined, undefined, + ts.factory.createIdentifier(SET_CONTROLLER_CTR), undefined, + ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(SET_CONTROLLER_CTR_TYPE), + undefined), undefined)], undefined, ts.factory.createBlock( + [ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), name), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + ts.factory.createIdentifier(SET_CONTROLLER_CTR)))], true)); + } +} + +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): void { + let stateManagementDecoratorCount: number = 0; + for (let i = 0; i < node.decorators.length; i++) { + const decoratorName: string = node.decorators[i].getText().replace(/\(.*\)$/, '').trim(); + if (decoratorName !== COMPONENT_WATCH_DECORATOR) { + curPropMap.set(name.escapedText.toString(), decoratorName); + } + if (BUILDIN_STYLE_NAMES.has(decoratorName.replace('@', ''))) { + validateDuplicateDecorator(node.decorators[i], log); + } + if (decoratorName !== COMPONENT_WATCH_DECORATOR && isForbiddenUseStateType(node.type)) { + // @ts-ignore + validateForbiddenUseStateType(name, decoratorName, node.type.typeName.getText(), log); + return; + } + if (parentName.getText() === componentCollection.entryComponent && + mandatoryToInitViaParamDecorators.has(decoratorName)) { + validateHasIllegalDecoratorInEntry(parentName, name, decoratorName, log); + } + if (node.initializer && forbiddenSpecifyDefaultValueDecorators.has(decoratorName)) { + validatePropertyDefaultValue(name, decoratorName, log); + return; + } else if (!node.initializer && mandatorySpecifyDefaultValueDecorators.has(decoratorName)) { + validatePropertyNonDefaultValue(name, decoratorName, log); + return; + } + if (node.questionToken && mandatoryToInitViaParamDecorators.has(decoratorName)) { + validateHasIllegalQuestionToken(name, decoratorName, log); + } + if (!isSimpleType(node.type, program)) { + stateObjectCollection.add(name.escapedText.toString()); + } + if (decoratorName === COMPONENT_WATCH_DECORATOR && + validateWatchDecorator(name, node.decorators.length, log)) { + 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); + } + } + if (stateManagementDecoratorCount > 1) { + validateMultiDecorators(name, log); + return; + } +} + +function processStateDecorators(node: ts.PropertyDeclaration, decorator: string, + updateResult: UpdateResult, ctorNode: ts.ConstructorDeclaration, log: LogInfo[], + program: ts.Program, context: ts.TransformationContext): 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); + if (variableInitStatement) { + updateState.push(variableInitStatement); + } + addAddProvidedVar(node, name, decorator, updateState); + updateResult.setCtor(updateConstructor(ctorNode, [], [...updateState], false)); + updateResult.setVariableGet(createGetAccessor(name, CREATE_GET_METHOD)); + if (!immutableDecorators.has(decorator)) { + updateResult.setVariableSet(createSetAccessor(name, CREATE_SET_METHOD)); + } + if (setUpdateParamsDecorators.has(decorator)) { + updateResult.setUpdateParams(createUpdateParams(name, decorator)); + } + updateResult.setDeleteParams(true); +} + +function processWatch(node: ts.PropertyDeclaration, decorator: ts.Decorator, + watchMap: Map, log: LogInfo[]): void { + if (node.name) { + const propertyName: string = node.name.getText(); + if (decorator.expression && ts.isCallExpression(decorator.expression) && + decorator.expression.arguments && decorator.expression.arguments.length === 1) { + const currentClassMethod: Set = classMethodCollection.get(node.parent.name.getText()); + const argument: ts.Node = decorator.expression.arguments[0]; + if (ts.isStringLiteral(argument)) { + if (currentClassMethod.has(argument.text)) { + watchMap.set(propertyName, argument); + } else { + log.push({ + type: LogType.ERROR, + message: `Cannot find name ${argument.getText()} in struct '${node.parent.name.getText()}'.`, + pos: argument.getStart() + }); + } + } else if (ts.isIdentifier(decorator.expression.arguments[0])) { + const content: string = decorator.expression.arguments[0].getText(); + const propertyNode: ts.PropertyAccessExpression = createPropertyAccessExpressionWithThis(content); + watchMap.set(propertyName, propertyNode); + decoratorParamSet.add(content); + validateWatchParam(LogType.WARN, argument.getStart(), log); + } else if (ts.isPropertyAccessExpression(decorator.expression.arguments[0])) { + watchMap.set(propertyName, decorator.expression.arguments[0]); + validateWatchParam(LogType.WARN, argument.getStart(), log); + } else { + validateWatchParam(LogType.ERROR, argument.getStart(), log); + } + } + } +} + +function createVariableInitStatement(node: ts.PropertyDeclaration, decorator: string, + log: LogInfo[], program: ts.Program, context: ts.TransformationContext): ts.Statement { + const name: ts.Identifier = node.name as ts.Identifier; + let type: ts.TypeNode; + let updateState: ts.ExpressionStatement; + if (node.type) { + type = node.type; + } + switch (decorator) { + case COMPONENT_NON_DECORATOR: + updateState = updateNormalProperty(node, name, log, context); + break; + case COMPONENT_STATE_DECORATOR: + case COMPONENT_PROVIDE_DECORATOR: + updateState = updateObservedProperty(node, name, type, program); + break; + case COMPONENT_LINK_DECORATOR: + updateState = updateSynchedPropertyTwoWay(name, type, program); + break; + case COMPONENT_PROP_DECORATOR: + updateState = updateSynchedPropertyOneWay(name, type, decorator, log, program); + break; + case COMPONENT_STORAGE_PROP_DECORATOR: + case COMPONENT_STORAGE_LINK_DECORATOR: + const setFuncName: string = decorator === COMPONENT_STORAGE_PROP_DECORATOR ? + APP_STORAGE_SET_AND_PROP : APP_STORAGE_SET_AND_LINK; + updateState = updateStoragePropAndLinkProperty(node, name, setFuncName, log); + break; + case COMPONENT_OBJECT_LINK_DECORATOR: + updateState = updateSynchedPropertyNesedObject(name, type, decorator, log); + break; + case COMPONENT_CONSUME_DECORATOR: + updateState = updateConsumeProperty(node, name); + break; + } + return updateState; +} + +function createUpdateParams(name: ts.Identifier, decorator: string): ts.Statement { + let updateParamsNode: ts.Statement; + switch (decorator) { + case COMPONENT_NON_DECORATOR: + case COMPONENT_STATE_DECORATOR: + case COMPONENT_PROVIDE_DECORATOR: + updateParamsNode = createUpdateParamsWithIf(name); + break; + case COMPONENT_PROP_DECORATOR: + updateParamsNode = createUpdateParamsWithoutIf(name); + break; + case COMPONENT_OBJECT_LINK_DECORATOR: + updateParamsNode = createUpdateParamsWithSet(name); + break; + } + return updateParamsNode; +} + +function createUpdateParamsWithIf(name: ts.Identifier): ts.IfStatement { + return ts.factory.createIfStatement(ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), + ts.factory.createIdentifier(name.escapedText.toString())), + ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED)), ts.factory.createBlock([ + createUpdateParamsWithoutIf(name)], true), undefined); +} + +function createUpdateParamsWithoutIf(name: ts.Identifier): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(name.getText()), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + createPropertyAccessExpressionWithParams(name.getText()))); +} + +function createUpdateParamsWithSet(name: ts.Identifier): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(createPropertyAccessExpressionWithThis(`__${name.getText()}`), + ts.factory.createIdentifier(CREATE_SET_METHOD)), undefined, + [createPropertyAccessExpressionWithParams(name.getText())])); +} + +function updateNormalProperty(node: ts.PropertyDeclaration, name: ts.Identifier, + log: LogInfo[], context: ts.TransformationContext): ts.ExpressionStatement { + const init: ts.Expression = + ts.visitNode(processCustomDialogController(node, log), visitDialogController); + function visitDialogController(node: ts.Node): ts.Node { + if (isProperty(node)) { + node = createReference(node as ts.PropertyAssignment); + } + return ts.visitEachChild(node, visitDialogController, context); + } + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(name.getText()), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), init || + ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_UNDEFINED))); +} + +function processCustomDialogController(node: ts.PropertyDeclaration, + log: LogInfo[]): ts.Expression { + if (node.initializer && ts.isNewExpression(node.initializer) && + ts.isIdentifier(node.initializer.expression) && + node.initializer.expression.getText() === SET_CONTROLLER_CTR_TYPE) { + return createCustomDialogController(node, node.initializer, log); + } + return node.initializer; +} + +function createCustomDialogController(parent: ts.PropertyDeclaration, node: ts.NewExpression, + log: LogInfo[]): ts.NewExpression { + if (node.arguments && node.arguments.length === 1 && + ts.isObjectLiteralExpression(node.arguments[0]) && node.arguments[0].properties) { + const newproperties: ts.ObjectLiteralElementLike[] = node.arguments[0].properties.map((item) => { + if (isCustomDialogControllerPropertyAssignment(item, log)) { + item = processCustomDialogControllerPropertyAssignment(parent, item as ts.PropertyAssignment); + } + return item; + }); + return ts.factory.createNewExpression(node.expression, node.typeArguments, + [ts.factory.createObjectLiteralExpression(newproperties, true), ts.factory.createThis()]); + } +} + +function isCustomDialogControllerPropertyAssignment(node: ts.ObjectLiteralElementLike, + log: LogInfo[]): boolean { + if (ts.isPropertyAssignment(node) && ts.isIdentifier(node.name) && + node.name.getText() === CUSTOM_DIALOG_CONTROLLER_BUILDER) { + if (ts.isCallExpression(node.initializer) && ts.isIdentifier(node.initializer.expression) && + componentCollection.customDialogs.has(node.initializer.expression.getText())) { + return true; + } else { + validateCustomDialogControllerBuilderInit(node, log); + } + } +} + +function processCustomDialogControllerPropertyAssignment(parent: ts.PropertyDeclaration, + node: ts.PropertyAssignment): ts.PropertyAssignment { + if (ts.isCallExpression(node.initializer)) { + return ts.factory.updatePropertyAssignment(node, node.name, + processCustomDialogControllerBuilder(parent, node.initializer)); + } +} + +function processCustomDialogControllerBuilder(parent: ts.PropertyDeclaration, + node: ts.CallExpression): ts.ArrowFunction { + const newExp: ts.Expression = createCustomComponentNewExpression(node); + const jsDialog: ts.Identifier = ts.factory.createIdentifier(JS_DIALOG); + return createCustomComponentBuilderArrowFunction(parent, jsDialog, newExp); +} + +function updateObservedProperty(item: ts.PropertyDeclaration, name: ts.Identifier, + type: ts.TypeNode, program: ts.Program): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(`__${name.getText()}`), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createNewExpression( + ts.factory.createIdentifier(isSimpleType(type, program) ? OBSERVED_PROPERTY_SIMPLE : + OBSERVED_PROPERTY_OBJECT), undefined, [item.initializer, ts.factory.createThis(), + ts.factory.createStringLiteral(name.escapedText.toString())]))); +} + +function updateSynchedPropertyTwoWay(nameIdentifier: ts.Identifier, type: ts.TypeNode, + program: ts.Program): ts.ExpressionStatement { + const name: string = nameIdentifier.escapedText.toString(); + const functionName: string = isSimpleType(type, program) ? + SYNCHED_PROPERTY_SIMPLE_TWO_WAY : SYNCHED_PROPERTY_OBJECT_TWO_WAY; + return createInitExpressionStatementForDecorator(name, functionName, + createPropertyAccessExpressionWithParams(name)); +} + +function updateSynchedPropertyOneWay(nameIdentifier: ts.Identifier, type: ts.TypeNode, + decoractor: string, log: LogInfo[], program: ts.Program): ts.ExpressionStatement { + const name: string = nameIdentifier.escapedText.toString(); + if (isSimpleType(type, program)) { + return createInitExpressionStatementForDecorator(name, SYNCHED_PROPERTY_SIMPLE_ONE_WAY, + createPropertyAccessExpressionWithParams(name)); + } else { + validateNonSimpleType(nameIdentifier, decoractor, log); + } +} + +function updateStoragePropAndLinkProperty(node: ts.PropertyDeclaration, name: ts.Identifier, + setFuncName: string, log: LogInfo[]): ts.ExpressionStatement { + if (isSingleKey(node)) { + const key: string = getDecoratorKey(node); + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(`__${name.getText()}`), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(APP_STORAGE), + ts.factory.createIdentifier(APP_STORAGE_GET_OR_SET)), undefined, []), + ts.factory.createIdentifier(setFuncName)), undefined, [ts.factory.createStringLiteral(key), + node.initializer, ts.factory.createThis()]))); + } else { + validateAppStorageDecoractorsNonSingleKey(node, log); + } +} + +function getDecoratorKey(node: ts.PropertyDeclaration): string { + let key: string; + // @ts-ignore + const keyNameNode: ts.Node = node.decorators[0].expression.arguments[0]; + if (ts.isIdentifier(keyNameNode)) { + key = keyNameNode.getText(); + decoratorParamSet.add(key); + } else if (ts.isStringLiteral(keyNameNode)) { + key = keyNameNode.text; + } + return key; +} + +function updateSynchedPropertyNesedObject(nameIdentifier: ts.Identifier, + type: ts.TypeNode, decoractor: string, log: LogInfo[]): ts.ExpressionStatement { + if (isObservedClassType(type)) { + return createInitExpressionStatementForDecorator(nameIdentifier.getText(), SYNCHED_PROPERTY_NESED_OBJECT, + createPropertyAccessExpressionWithParams(nameIdentifier.getText())); + } else { + validateNonObservedClassType(nameIdentifier, decoractor, log); + } +} + +function updateConsumeProperty(node: ts.PropertyDeclaration, + nameIdentifier: ts.Identifier): ts.ExpressionStatement { + const name: string = nameIdentifier.getText(); + let propertyOrAliasName: string; + if (isSingleKey(node)) { + propertyOrAliasName = getDecoratorKey(node); + } else { + propertyOrAliasName = name; + } + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(`__${name}`), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createCallExpression( + createPropertyAccessExpressionWithThis(INITIALIZE_CONSUME_FUNCTION), undefined, [ + ts.factory.createStringLiteral(propertyOrAliasName), ts.factory.createStringLiteral(name)]))); +} + +function createCustomComponentBuilderArrowFunction(parent: ts.PropertyDeclaration, + jsDialog: ts.Identifier, newExp: ts.Expression): ts.ArrowFunction { + return ts.factory.createArrowFunction(undefined, undefined, [], undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createBlock([ + ts.factory.createVariableStatement(undefined, ts.factory.createVariableDeclarationList( + [ts.factory.createVariableDeclaration(jsDialog, undefined, undefined, newExp)], + ts.NodeFlags.Let)), ts.factory.createExpressionStatement(ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(jsDialog, + ts.factory.createIdentifier(SET_CONTROLLER_METHOD)), undefined, + [ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + parent.name as ts.Identifier)])), ts.factory.createExpressionStatement( + createViewCreate(jsDialog))], true)); +} + +export function createViewCreate(node: ts.NewExpression | ts.Identifier): ts.CallExpression { + return createFunction(ts.factory.createIdentifier(BASE_COMPONENT_NAME), + ts.factory.createIdentifier(COMPONENT_CREATE_FUNCTION), ts.factory.createNodeArray([node])); +} + +export function createCustomComponentNewExpression(node: ts.CallExpression): ts.NewExpression { + const newNode: ts.NewExpression = ts.factory.createNewExpression(node.expression, + node.typeArguments, node.arguments.length ? node.arguments : []); + return addCustomComponentId(newNode); +} + +function addCustomComponentId(node: ts.NewExpression): ts.NewExpression { + for (const item of componentCollection.customComponents) { + componentInfo.componentNames.add(item); + } + componentInfo.componentNames.forEach((name: string) => { + const nodeIdentifier: ts.Identifier | ts.PropertyAccessExpression = + node.expression as ts.Identifier | ts.PropertyAccessExpression; + let argumentsArray: ts.Expression[]; + if (node.arguments && node.arguments.length) { + argumentsArray = Array.from(node.arguments); + } + if (nodeIdentifier && (ts.isIdentifier(nodeIdentifier) && + nodeIdentifier.escapedText === name || ts.isPropertyAccessExpression(nodeIdentifier) && + ts.isIdentifier(nodeIdentifier.name) && nodeIdentifier.name.escapedText === name)) { + if (!argumentsArray) { + argumentsArray = [ts.factory.createObjectLiteralExpression([], true)]; + } + argumentsArray.unshift(ts.factory.createStringLiteral((++componentInfo.id).toString()), + ts.factory.createThis()); + node = + ts.factory.updateNewExpression(node, node.expression, node.typeArguments, argumentsArray); + } else if (argumentsArray) { + node = + ts.factory.updateNewExpression(node, node.expression, node.typeArguments, argumentsArray); + } + }); + return node; +} + +function createInitExpressionStatementForDecorator(propertyName: string, functionName: string, + parameterNode: ts.Expression): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createBinaryExpression( + createPropertyAccessExpressionWithThis(`__${propertyName}`), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), ts.factory.createNewExpression( + ts.factory.createIdentifier(functionName), undefined, [parameterNode, ts.factory.createThis(), + ts.factory.createStringLiteral(propertyName)]))); +} + +function createPropertyAccessExpressionWithParams(propertyName: string): ts.PropertyAccessExpression { + return ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(CREATE_CONSTRUCTOR_PARAMS), + ts.factory.createIdentifier(propertyName)); +} + +function createPropertyAccessExpressionWithThis(propertyName: string): ts.PropertyAccessExpression { + return ts.factory.createPropertyAccessExpression(ts.factory.createThis(), + ts.factory.createIdentifier(propertyName)); +} + +function addAddProvidedVar(node: ts.PropertyDeclaration, name: ts.Identifier, + decoratorName: string, updateState: ts.Statement[]): void { + if (decoratorName === COMPONENT_PROVIDE_DECORATOR) { + if (isSingleKey(node)) { + updateState.push(createAddProvidedVar(getDecoratorKey(node), name)); + } + updateState.push(createAddProvidedVar(name.getText(), name)); + } +} + +function createAddProvidedVar(propertyOrAliasName: string, + name: ts.Identifier): ts.ExpressionStatement { + return ts.factory.createExpressionStatement(ts.factory.createCallExpression( + createPropertyAccessExpressionWithThis(ADD_PROVIDED_VAR), undefined, [ + ts.factory.createStringLiteral(propertyOrAliasName), + createPropertyAccessExpressionWithThis(`__${name.getText()}`)])); +} + +function createGetAccessor(item: ts.Identifier, express: string): ts.GetAccessorDeclaration { + const getAccessorStatement: ts.GetAccessorDeclaration = + ts.factory.createGetAccessorDeclaration(undefined, undefined, item, [], undefined, + ts.factory.createBlock([ts.factory.createReturnStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + createPropertyAccessExpressionWithThis(`__${item.getText()}`), + ts.factory.createIdentifier(express)), undefined, []))], true)); + return getAccessorStatement; +} + +function createSetAccessor(item: ts.Identifier, express: string): 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, + undefined)], ts.factory.createBlock([ts.factory.createExpressionStatement( + ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression( + createPropertyAccessExpressionWithThis(`__${item.getText()}`), + ts.factory.createIdentifier(express)), undefined, + [ts.factory.createIdentifier(CREATE_NEWVALUE_IDENTIFIER)]))], true)); + return setAccessorStatement; +} + +function isForbiddenUseStateType(typeNode: ts.TypeNode): boolean { + if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName) && + forbiddenUseStateType.has(typeNode.typeName.getText())) { + return true; + } + return false; +} + +function isSimpleType(typeNode: ts.TypeNode, program: ts.Program): boolean { + let checker: ts.TypeChecker; + if (program) { + checker = program.getTypeChecker(); + } + const enumType: ts.SyntaxKind = getEnumType(typeNode, checker); + if (simpleTypes.has(enumType || typeNode.kind) || isEnumtype(typeNode)) { + return true; + } else if (ts.isUnionTypeNode(typeNode) && typeNode.types) { + const types: ts.NodeArray = typeNode.types; + for (let i = 0; i < types.length; i++) { + const enumType: ts.SyntaxKind = getEnumType(types[i], checker); + if (!simpleTypes.has(enumType || types[i].kind) && !isEnumtype(typeNode)) { + return false; + } + } + return true; + } + return false; +} + +function getEnumType(typeNode: ts.TypeNode, checker: ts.TypeChecker): ts.SyntaxKind { + if (!checker) { + return; + } + if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) { + const type: ts.Type = + checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(typeNode.typeName)); + if (type.symbol && type.symbol.valueDeclaration) { + return type.symbol.valueDeclaration.kind; + } + } +} + +function isEnumtype(typeNode: ts.TypeNode): boolean { + if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) { + return enumCollection.has(typeNode.typeName.getText()); + } +} + +function isObservedClassType(type: ts.TypeNode): boolean { + if (ts.isTypeReferenceNode(type) && observedClassCollection.has(type.getText())) { + return true; + } else if (ts.isUnionTypeNode(type) && type.types) { + const types: ts.NodeArray = type.types; + for (let i = 0; i < types.length; i++) { + if (!observedClassCollection.has(types[i].getText())) { + return false; + } + } + return true; + } + return false; +} + +function validateAppStorageDecoractorsNonSingleKey(node: ts.PropertyDeclaration, + log: LogInfo[]): void { + if (ts.isIdentifier(node.decorators[0].expression)) { + validateDecoratorNonSingleKey(node.decorators[0].expression, log); + } else if (ts.isCallExpression(node.decorators[0].expression) && + ts.isIdentifier(node.decorators[0].expression.expression)) { + validateDecoratorNonSingleKey(node.decorators[0].expression.expression, log); + } +} + +function isSingleKey(node: ts.PropertyDeclaration): boolean { + if (ts.isCallExpression(node.decorators[0].expression) && + node.decorators[0].expression.arguments && + node.decorators[0].expression.arguments.length === 1 && + (ts.isIdentifier(node.decorators[0].expression.arguments[0]) || + ts.isStringLiteral(node.decorators[0].expression.arguments[0]))) { + return true; + } +} + +function validateMultiDecorators(name: ts.Identifier, log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The property '${name.escapedText.toString()}' cannot have mutilate state management decorators.`, + pos: name.getStart() + }); +} + +function validateDecoratorNonSingleKey(decoratorsIdentifier: ts.Identifier, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The decorator ${decoratorsIdentifier.escapedText.toString()} should have a single key.`, + pos: decoratorsIdentifier.getStart() + }); +} + +function validatePropertyNonDefaultValue(propertyName: ts.Identifier, decorator: string, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The ${decorator} property '${propertyName.getText()}' must be specified a default value.`, + pos: propertyName.getStart() + }); +} + +function validatePropertyDefaultValue(propertyName: ts.Identifier, decorator: string, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The ${decorator} property '${propertyName.getText()}' cannot be specified a default value.`, + pos: propertyName.getStart() + }); +} + +function validatePropertyNonType(propertyName: ts.Identifier, log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The property '${propertyName.getText()}' must specify a type.`, + pos: propertyName.getStart() + }); +} + +function validateNonSimpleType(propertyName: ts.Identifier, decorator: string, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The type of the ${decorator} property '${propertyName.getText()}' ` + + `can only be string, number or boolean.`, + pos: propertyName.getStart() + }); +} + +function validateNonObservedClassType(propertyName: ts.Identifier, decorator: string, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The type of the ${decorator} property '${propertyName.getText()}' can only be ` + + `objects of classes decorated with ${COMPONENT_OBSERVED_DECORATOR} class decorator in ets (not ts).`, + pos: propertyName.getStart() + }); +} + +function validateHasIllegalQuestionToken(propertyName: ts.Identifier, decorator: string, + log: LogInfo[]): void { + log.push({ + type: LogType.WARN, + message: `The ${decorator} property '${propertyName.getText()}' cannot have a question token.`, + pos: propertyName.getStart() + }); +} + +function validateHasIllegalDecoratorInEntry(parentName: ts.Identifier, propertyName: ts.Identifier, + decorator: string, log: LogInfo[]): void { + log.push({ + type: LogType.WARN, + message: `The @Entry component '${parentName.getText()}' cannot have the ` + + `${decorator} property '${propertyName.getText()}'.`, + pos: propertyName.getStart() + }); +} + +function validateForbiddenUseStateType(propertyName: ts.Identifier, decorator: string, type: string, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The ${decorator} property '${propertyName.getText()}' cannot be a '${type}' object.`, + pos: propertyName.getStart() + }); +} + +function validateDuplicateDecorator(decorator: ts.Decorator, log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: `The decorator '${decorator.getText()}' cannot have the same name as the build-in ` + + `style attribute '${decorator.getText().replace('@', '')}'.`, + pos: decorator.getStart() + }); +} + +function validateWatchDecorator(propertyName: ts.Identifier, length: number, log: LogInfo[]): boolean { + if (length === 1) { + log.push({ + type: LogType.ERROR, + message: `Regular variable '${propertyName.escapedText.toString()}' can not be decorated with @Watch.`, + pos: propertyName.getStart() + }); + return false; + } + return true; +} + +function validateWatchParam(type: LogType, pos: number, log: LogInfo[]): void { + log.push({ + type: type, + message: 'The parameter should be a string.', + pos: pos + }); +} + +function validateCustomDialogControllerBuilderInit(node: ts.ObjectLiteralElementLike, + log: LogInfo[]): void { + log.push({ + type: LogType.ERROR, + message: 'The builder should be initialized with a @CustomDialog Component.', + pos: node.getStart() + }); +} diff --git a/compiler/src/process_import.ts b/compiler/src/process_import.ts index 47197ffa51a5f0ebc15c0174c982af649d2d6b16..f475da2b5016b110152c55a57d1a9607ce79c7fe 100644 --- a/compiler/src/process_import.ts +++ b/compiler/src/process_import.ts @@ -25,7 +25,7 @@ import { STRUCT, CLASS, CUSTOM_COMPONENT_DEFAULT, - COMPONENT_DECORATOR_NAME_COMPONENT + CUSTOM_DECORATOR_NAME } from './pre_define'; import { propertyCollection, @@ -36,6 +36,7 @@ import { processSystemApi, propCollection, isObservedClass, + isCustomDialogClass, observedClassCollection, enumCollection, getComponentSet, @@ -104,6 +105,10 @@ function visitAllNode(node: ts.Node, defaultNameFromParent: string, asNameFromPa // @ts-ignore observedClassCollection.add(node.name.getText()); } + if (isCustomDialogClass(node)) { + // @ts-ignore + componentCollection.customDialogs.add(node.name.getText()); + } if (ts.isEnumDeclaration(node) && node.name) { enumCollection.add(node.name.getText()); } @@ -209,7 +214,7 @@ function isCustomComponent(node: ts.ClassDeclaration): boolean { for (let i = 0; i < node.decorators.length; ++i) { const decoratorName: ts.Identifier = node.decorators[i].expression as ts.Identifier; if (ts.isIdentifier(decoratorName) && - decoratorName.escapedText.toString() === COMPONENT_DECORATOR_NAME_COMPONENT) { + CUSTOM_DECORATOR_NAME.has(decoratorName.escapedText.toString())) { return true; } } diff --git a/compiler/src/process_worker.ts b/compiler/src/process_system_module.ts similarity index 90% rename from compiler/src/process_worker.ts rename to compiler/src/process_system_module.ts index 65e3c66f5960e5766242b70595e03a20ffb7bc93..9ef05d3cc4e421f0af8f8dd7f45eaa49829f88a5 100644 --- a/compiler/src/process_worker.ts +++ b/compiler/src/process_system_module.ts @@ -15,6 +15,6 @@ import { processSystemApi } from './validate_ui_syntax'; -module.exports = function processWorker(source: string): string { +module.exports = function processSystemModule(source: string): string { return processSystemApi(source); }; diff --git a/compiler/src/process_ui_syntax.ts b/compiler/src/process_ui_syntax.ts index b2012a7fac4ca051e2e6eb92e58242e758de4825..b8335049ab4e51e36e784b06d4432385b9132b53 100644 --- a/compiler/src/process_ui_syntax.ts +++ b/compiler/src/process_ui_syntax.ts @@ -28,7 +28,11 @@ import { COMPONENT_EXTEND_DECORATOR, RESOURCE, RESOURCE_TYPE, - WORKER_OBJECT + WORKER_OBJECT, + RESOURCE_NAME_ID, + RESOURCE_NAME_TYPE, + RESOURCE_NAME_PARAMS, + RESOURCE_RAWFILE } from './pre_define'; import { componentInfo, @@ -48,19 +52,19 @@ import { BUILDIN_CONTAINER_COMPONENT, CUSTOM_BUILDER_METHOD, EXTEND_ATTRIBUTE, - INNER_COMPONENT_NAMES + JS_BIND_COMPONENTS } from './component_map'; import { resources } from '../main'; export const transformLog: FileLog = new FileLog(); -export function processUISyntax(program: ts.Program): Function { +export function processUISyntax(program: ts.Program, ut = false): Function { return (context: ts.TransformationContext) => { let pagesDir: string; return (node: ts.SourceFile) => { pagesDir = path.resolve(path.dirname(node.fileName)); if (process.env.compiler === BUILD_ON) { - if (path.basename(node.fileName) === 'app.ets.ts') { + if (!ut && (path.basename(node.fileName) === 'app.ets.ts' || !/\.ets\.ts$/.test(node.fileName))) { return node; } collectComponents(node); @@ -120,7 +124,7 @@ function collectComponents(node: ts.SourceFile): void { if (node.identifiers && node.identifiers.size) { // @ts-ignore for (let key of node.identifiers.keys()) { - if (INNER_COMPONENT_NAMES.has(key)) { + if (JS_BIND_COMPONENTS.has(key)) { appComponentCollection.add(key); } } @@ -129,25 +133,53 @@ function collectComponents(node: ts.SourceFile): void { function isResource(node: ts.Node): boolean { return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && - node.expression.escapedText.toString() === RESOURCE && node.arguments.length > 0; + (node.expression.escapedText.toString() === RESOURCE || + node.expression.escapedText.toString() === RESOURCE_RAWFILE) && node.arguments.length > 0; } function processResourceData(node: ts.CallExpression): ts.Node { if (ts.isStringLiteral(node.arguments[0])) { - // @ts-ignore - const resourceData: string[] = node.arguments[0].text.split('.'); - if (validateResourceData(resourceData, resources, node.arguments[0].getStart())) { - const resourceType: number = RESOURCE_TYPE[resourceData[1]]; - const resourceValue: number = resources[resourceData[0]][resourceData[1]][resourceData[2]]; - const argsArr: ts.Expression[] = Array.from(node.arguments); - argsArr.splice(0, 1, ts.factory.createNumericLiteral(resourceValue), - ts.factory.createNumericLiteral(resourceType)); - return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, argsArr); + if (node.expression.getText() === RESOURCE_RAWFILE) { + return createResourceParam(0, RESOURCE_TYPE.rawfile, [node.arguments[0]]); + } else { + // @ts-ignore + const resourceData: string[] = node.arguments[0].text.trim().split('.'); + if (validateResourceData(resourceData, resources, node.arguments[0].getStart())) { + const resourceType: number = RESOURCE_TYPE[resourceData[1]]; + const resourceValue: number = resources[resourceData[0]][resourceData[1]][resourceData[2]]; + return createResourceParam(resourceValue, resourceType, + Array.from(node.arguments).slice(1)); + } } } return node; } +function createResourceParam(resourceValue: number, resourceType: number,argsArr: ts.Expression[]): + ts.ObjectLiteralExpression { + const resourceParams: ts.ObjectLiteralExpression = ts.factory.createObjectLiteralExpression( + [ + ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(RESOURCE_NAME_ID), + ts.factory.createNumericLiteral(resourceValue) + ), + ts.factory.createPropertyAssignment( + ts.factory.createStringLiteral(RESOURCE_NAME_TYPE), + ts.factory.createNumericLiteral(resourceType) + ), + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier(RESOURCE_NAME_PARAMS), + ts.factory.createArrayLiteralExpression( + argsArr, + false + ) + ) + ], + false + ); + return resourceParams; +} + function validateResourceData(resourceData: string[], resources: object, pos: number): boolean { if (resourceData.length !== 3) { transformLog.errors.push({ diff --git a/compiler/src/result_process.ts b/compiler/src/result_process.ts index 1d70cfe0fd4ff63374d8300c894ebb84f15390c1..6dcc8065754a0c32cad8470274c929c3855a7c2b 100644 --- a/compiler/src/result_process.ts +++ b/compiler/src/result_process.ts @@ -1,65 +1,67 @@ -/* - * 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 { BUILD_OFF } from './pre_define'; -import { - resetLog, - transformLog -} from './process_ui_syntax'; -import { - propertyCollection, - linkCollection -} from './validate_ui_syntax'; -import { - LogInfo, - emitLogInfo, - componentInfo -} from './utils'; -import { resetComponentCollection } from './validate_ui_syntax'; - -module.exports = function resultProcess(source: string, map: any): string { - process.env.compiler = BUILD_OFF; - componentInfo.id = 0; - propertyCollection.clear(); - linkCollection.clear(); - resetComponentCollection(); - if (transformLog && transformLog.errors.length) { - const sourceFile: ts.SourceFile = transformLog.sourceFile; - - const logInfos: LogInfo[] = transformLog.errors.map((item) => { - if (item.pos) { - const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(item.pos); - item.line = posOfNode.line + 1; - item.column = posOfNode.character + 1; - } else { - item.line = undefined; - item.column = undefined; - } - item.fileName = sourceFile.fileName.replace(/.ts$/, ''); - return item; - }); - emitLogInfo(this, logInfos); - resetLog(); - } - if (path.basename(this.resourcePath) === 'app.ets') { - source = source.replace(/exports\.default/, 'globalThis.exports.default'); - } - - this.callback(null, source, map); - return; -}; +/* + * 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 { BUILD_OFF } from './pre_define'; +import { + resetLog, + transformLog +} from './process_ui_syntax'; +import { + propertyCollection, + linkCollection +} from './validate_ui_syntax'; +import { + LogInfo, + emitLogInfo, + componentInfo +} from './utils'; +import { resetComponentCollection } from './validate_ui_syntax'; +import { abilityConfig } from '../main'; + +module.exports = function resultProcess(source: string, map: any): void { + process.env.compiler = BUILD_OFF; + if (/\.ets$/.test(this.resourcePath)) { + componentInfo.id = 0; + propertyCollection.clear(); + linkCollection.clear(); + resetComponentCollection(); + if (transformLog && transformLog.errors.length) { + const sourceFile: ts.SourceFile = transformLog.sourceFile; + const logInfos: LogInfo[] = transformLog.errors.map((item) => { + if (item.pos) { + const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(item.pos); + item.line = posOfNode.line + 1; + item.column = posOfNode.character + 1; + } else { + item.line = undefined; + item.column = undefined; + } + item.fileName = sourceFile.fileName.replace(/.ts$/, ''); + return item; + }); + emitLogInfo(this, logInfos); + resetLog(); + } + } + const resourcePath: string = path.basename(this.resourcePath); + if (['app.ets', abilityConfig.abilityEntryFile].includes(resourcePath)) { + source = source.replace(/exports\.default/, 'globalThis.exports.default'); + } + + this.callback(null, source, map); +}; diff --git a/compiler/src/utils.ts b/compiler/src/utils.ts index 943c1aa7f6e5ec74c5a65e7e4299ac19c2edc9c0..c58d0244e93e740c6ecd0f5134236c74b9a275db 100644 --- a/compiler/src/utils.ts +++ b/compiler/src/utils.ts @@ -1,158 +1,158 @@ -/* - * 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 fs from 'fs'; - -export enum LogType { - ERROR = 'ERROR', - WARN = 'WARN', - NOTE = 'NOTE' -} - -export interface LogInfo { - type: LogType, - message: string, - pos?: number, - line?: number, - column?: number, - fileName?: string -} - -export class FileLog { - private _sourceFile: ts.SourceFile; - private _errors: LogInfo[] = []; - - public get sourceFile() { - return this._sourceFile; - } - - public set sourceFile(newValue: ts.SourceFile) { - this._sourceFile = newValue; - } - - public get errors() { - return this._errors; - } - - public set errors(newValue: LogInfo[]) { - this._errors = newValue; - } -} - -export function emitLogInfo(loader: any, infos: LogInfo[]) { - if (infos && infos.length) { - infos.forEach((item) => { - switch (item.type) { - case LogType.ERROR: - loader.emitError(getMessage(loader.resourcePath, item)); - break; - case LogType.WARN: - loader.emitWarning(getMessage(loader.resourcePath, item)); - break; - case LogType.NOTE: - loader.emitWarning(getMessage(loader.resourcePath, item)); - break; - } - }); - } -} - -export function addLog(type: LogType, message: string, pos: number, log: LogInfo[], - sourceFile: ts.SourceFile) { - const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(pos); - log.push({ - type: type, - message: message, - line: posOfNode.line + 1, - column: posOfNode.character + 1, - fileName: sourceFile.fileName - }); -} - -export function getMessage(fileName: string, info: LogInfo): string { - let messsage: string; - if (info.line && info.column) { - messsage = `BUILD${info.type} File: ${fileName}:${info.line}:${info.column}\n ${info.message}`; - } else { - messsage = `BUILD${info.type} File: ${fileName}\n ${info.message}`; - } - return messsage; -} - -class ComponentInfo { - private _id: number = 0; - private _componentNames: Set = new Set(['ForEach']); - public set id(id: number) { - this._id = id; - } - public get id() { - return this._id; - } - public set componentNames(componentNames: Set) { - this._componentNames = componentNames; - } - public get componentNames() { - return this._componentNames; - } -} - -export const componentInfo: ComponentInfo = new ComponentInfo(); - -export function hasDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | ts.ClassDeclaration, - decortorName: string): boolean { - if (node.decorators && node.decorators.length) { - for (let i = 0; i < node.decorators.length; i++) { - if (node.decorators[i].getText() === decortorName) { - return true; - } - } - } - return false; -} - -const STATEMENT_EXPECT: number = 1128; -const SEMICOLON_EXPECT: number = 1005; -export const IGNORE_ERROR_CODE: number[] = [STATEMENT_EXPECT, SEMICOLON_EXPECT]; - -export function readFile(dir: string, utFiles: string[]) { - try { - const files: string[] = fs.readdirSync(dir); - files.forEach((element) => { - const filePath: string = path.join(dir, element); - const status: fs.Stats = fs.statSync(filePath); - if (status.isDirectory()) { - readFile(filePath, utFiles); - } else { - utFiles.push(filePath); - } - }); - } catch (e) { - console.error('ETS ERROR: ' + e); - } -} - -export function createFunction(node: ts.Identifier, attrNode: ts.Identifier, - argumentsArr: ts.NodeArray): ts.CallExpression { - return ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - node, - attrNode - ), - undefined, - argumentsArr && argumentsArr.length ? argumentsArr : [] - ); -} +/* + * 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 fs from 'fs'; + +export enum LogType { + ERROR = 'ERROR', + WARN = 'WARN', + NOTE = 'NOTE' +} + +export interface LogInfo { + type: LogType, + message: string, + pos?: number, + line?: number, + column?: number, + fileName?: string +} + +export class FileLog { + private _sourceFile: ts.SourceFile; + private _errors: LogInfo[] = []; + + public get sourceFile() { + return this._sourceFile; + } + + public set sourceFile(newValue: ts.SourceFile) { + this._sourceFile = newValue; + } + + public get errors() { + return this._errors; + } + + public set errors(newValue: LogInfo[]) { + this._errors = newValue; + } +} + +export function emitLogInfo(loader: any, infos: LogInfo[]) { + if (infos && infos.length) { + infos.forEach((item) => { + switch (item.type) { + case LogType.ERROR: + loader.emitError(getMessage(loader.resourcePath, item)); + break; + case LogType.WARN: + loader.emitWarning(getMessage(loader.resourcePath, item)); + break; + case LogType.NOTE: + loader.emitWarning(getMessage(loader.resourcePath, item)); + break; + } + }); + } +} + +export function addLog(type: LogType, message: string, pos: number, log: LogInfo[], + sourceFile: ts.SourceFile) { + const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(pos); + log.push({ + type: type, + message: message, + line: posOfNode.line + 1, + column: posOfNode.character + 1, + fileName: sourceFile.fileName + }); +} + +export function getMessage(fileName: string, info: LogInfo): string { + let messsage: string; + if (info.line && info.column) { + messsage = `BUILD${info.type} File: ${fileName}:${info.line}:${info.column}\n ${info.message}`; + } else { + messsage = `BUILD${info.type} File: ${fileName}\n ${info.message}`; + } + return messsage; +} + +class ComponentInfo { + private _id: number = 0; + private _componentNames: Set = new Set(['ForEach']); + public set id(id: number) { + this._id = id; + } + public get id() { + return this._id; + } + public set componentNames(componentNames: Set) { + this._componentNames = componentNames; + } + public get componentNames() { + return this._componentNames; + } +} + +export const componentInfo: ComponentInfo = new ComponentInfo(); + +export function hasDecorator(node: ts.MethodDeclaration | ts.FunctionDeclaration | ts.ClassDeclaration, + decortorName: string): boolean { + if (node.decorators && node.decorators.length) { + for (let i = 0; i < node.decorators.length; i++) { + if (node.decorators[i].getText() === decortorName) { + return true; + } + } + } + return false; +} + +const STATEMENT_EXPECT: number = 1128; +const SEMICOLON_EXPECT: number = 1005; +export const IGNORE_ERROR_CODE: number[] = [STATEMENT_EXPECT, SEMICOLON_EXPECT]; + +export function readFile(dir: string, utFiles: string[]) { + try { + const files: string[] = fs.readdirSync(dir); + files.forEach((element) => { + const filePath: string = path.join(dir, element); + const status: fs.Stats = fs.statSync(filePath); + if (status.isDirectory()) { + readFile(filePath, utFiles); + } else { + utFiles.push(filePath); + } + }); + } catch (e) { + console.error('ETS ERROR: ' + e); + } +} + +export function createFunction(node: ts.Identifier, attrNode: ts.Identifier, + argumentsArr: ts.NodeArray): ts.CallExpression { + return ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + node, + attrNode + ), + undefined, + argumentsArr && argumentsArr.length ? argumentsArr : [] + ); +} diff --git a/compiler/src/validate_ui_syntax.ts b/compiler/src/validate_ui_syntax.ts index 6cd1e67f56fe4595abf8da2e0ba9ead3d1ea1cdf..8b6fba6370fe1513b0d05e928c051f1faad4cdce 100644 --- a/compiler/src/validate_ui_syntax.ts +++ b/compiler/src/validate_ui_syntax.ts @@ -1,749 +1,759 @@ -/* - * 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, - STRUCT, - CLASS, - 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_CONSTRUCTOR_ID, - COMPONENT_CONSTRUCTOR_PARENT, - COMPONENT_CONSTRUCTOR_PARAMS, - COMPONENT_EXTEND_DECORATOR, - COMPONENT_OBSERVED_DECORATOR -} from './pre_define'; -import { - INNER_COMPONENT_NAMES, - AUTOMIC_COMPONENT, - SINGLE_CHILD_COMPONENT, - SPECIFIC_CHILD_COMPONENT, - BUILDIN_STYLE_NAMES, - EXTEND_ATTRIBUTE -} from './component_map'; -import { - LogType, - LogInfo, - componentInfo, - addLog, - hasDecorator -} from './utils'; - -export interface ComponentCollection { - entryComponent: string; - previewComponent: string; - customDialogs: Set; - customComponents: Set; - currentClassName: string; -} - -export interface IComponentSet { - propertys: Set; - regulars: Set; - states: Set; - links: Set; - props: Set; - storageProps: Set; - storageLinks: Set; - provides: Set; - consumes: Set; - objectLinks: Set; -} - -export const componentCollection: ComponentCollection = { - entryComponent: null, - previewComponent: null, - customDialogs: new Set([]), - customComponents: new Set([]), - currentClassName: null -}; - -export const observedClassCollection: Set = new Set(); -export const enumCollection: Set = new Set(); -export const classMethodCollection: Map> = new Map(); -export const dollarCollection: Set = new Set(); - -export const propertyCollection: Map> = new Map(); -export const stateCollection: Map> = new Map(); -export const linkCollection: Map> = new Map(); -export const propCollection: Map> = new Map(); -export const regularCollection: Map> = new Map(); -export const storagePropCollection: Map> = new Map(); -export const storageLinkCollection: Map> = new Map(); -export const provideCollection: Map> = new Map(); -export const consumeCollection: Map> = new Map(); -export const objectLinkCollection: Map> = new Map(); - -export const isStaticViewCollection: Map = new Map(); - -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 = - 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.TS); - 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 (isStruct(item)) { - if (index + 1 < arr.length && ts.isExpressionStatement(arr[index + 1]) && - // @ts-ignore - arr[index + 1].expression && ts.isIdentifier(arr[index + 1].expression)) { - if (ts.isExportAssignment(item) && hasComponentDecorator(item)) { - checkDecorators(item, result, arr[index + 1] as ts.ExpressionStatement, log, sourceFile); - } else if (index > 0 && hasComponentDecorator(arr[index - 1])) { - checkDecorators(arr[index - 1] as ts.MissingDeclaration, result, - arr[index + 1] as ts.ExpressionStatement, log, sourceFile); - } else { - // @ts-ignore - const pos: number = item.expression.getStart(); - const message: string = `A struct should use decorator '@Component'.`; - addLog(LogType.WARN, message, pos, log, sourceFile); - } - } else { - // @ts-ignore - const pos: number = item.expression.getStart(); - const message: string = `A struct must have a name.`; - addLog(LogType.ERROR, message, pos, log, sourceFile); - } - } - if (ts.isMissingDeclaration(item) && /struct/.test(item.getText())) { - const message: string = `Please use a valid decorator.`; - addLog(LogType.ERROR, message, item.getStart(), log, sourceFile); - } - }); - validateEntryCount(result, fileQuery, sourceFile.fileName, log); - validatePreviewCount(result, sourceFile.fileName, log); - } - - return log.length ? log : null; -} - -function validateEntryCount(result: DecoratorResult, fileQuery: string, - fileName: string, log: LogInfo[]): void { - if (result.entryCount !== 1 && fileQuery === '?entry') { - log.push({ - type: LogType.ERROR, - message: `A page must have one and only one '@Entry' decorator with a struct.`, - fileName: fileName - }); - } -} - -function validatePreviewCount(result: DecoratorResult, fileName: string, log: LogInfo[]): void { - if (result.previewCount > 1) { - log.push({ - type: LogType.ERROR, - message: `A page can have at most one '@Preview' decorator with a struct.`, - fileName: fileName - }); - } -} - -export function isObservedClass(node: ts.Node): boolean { - if (ts.isClassDeclaration(node) && hasDecorator(node, COMPONENT_OBSERVED_DECORATOR)) { - return true; - } - return false; -} - -function isStruct(node: ts.Node): boolean { - if ((ts.isExpressionStatement(node) || ts.isExportAssignment(node)) && - node.expression && ts.isIdentifier(node.expression) && node.expression.getText() === STRUCT) { - return true; - } - return false; -} - -function hasComponentDecorator(node: ts.Node): boolean { - if ((ts.isMissingDeclaration(node) || ts.isExportAssignment(node)) && - node.decorators && node.decorators.length) { - return true; - } - return false; -} - -interface DecoratorResult { - entryCount: number; - previewCount: number; -} - -function checkDecorators(node: ts.MissingDeclaration | ts.ExportAssignment, result: DecoratorResult, - component: ts.ExpressionStatement, log: LogInfo[], sourceFile: ts.SourceFile): void { - let hasComponentDecorator: boolean = false; - const componentName: string = component.getText(); - node.decorators.forEach((element) => { - const name: string = element.getText(); - if (INNER_COMPONENT_DECORATORS.has(name)) { - componentCollection.customComponents.add(componentName); - switch (name) { - case COMPONENT_DECORATOR_ENTRY: - result.entryCount++; - componentCollection.entryComponent = componentName; - 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); - } -} - -function checkUISyntax(filePath: string, allComponentNames: Set, content: string, - log: LogInfo[]): void { - const sourceFile: ts.SourceFile = ts.createSourceFile(filePath, content, - ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); - visitAllNode(sourceFile, sourceFile, allComponentNames, log); -} - -function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponentNames: Set, - log: LogInfo[]) { - checkAllNode(node, allComponentNames, sourceFileNode, log); - if (ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name)) { - collectComponentProps(node); - } - node.getChildren().forEach((item: ts.Node) => visitAllNode(item, sourceFileNode, allComponentNames, log)); -} - -function checkAllNode(node: ts.Node, allComponentNames: Set, 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.isCallExpression(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 callExpression: ts.CallExpression = node.expression as ts.CallExpression; - const nodeName: ts.Identifier = callExpression.expression as ts.Identifier; - if (AUTOMIC_COMPONENT.has(nodeName.escapedText.toString()) && getNextNode(node)) { - return true; - } - return false; -} - -function getNextNode(node: ts.Node): ts.Block { - if (node.parent && ts.isBlock(node.parent) && node.parent.statements) { - const statementsArray: ts.Node[] = Array.from(node.parent.statements); - for (let i = 0; i < statementsArray.length - 1; i++) { - const curNode: ts.Node = statementsArray[i]; - const nextNode: ts.Node = statementsArray[i + 1]; - if (node === curNode && ts.isBlock(nextNode)) { - return nextNode; - } - } - } -} - -function checkOneChildComponent(node: ts.Node, allComponentNames: Set, - sourceFileNode: ts.SourceFile, log: LogInfo[]): void { - if (ts.isExpressionStatement(node) && ts.isCallExpression(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): boolean { - const callExpression: ts.CallExpression = node.expression as ts.CallExpression; - const nodeName: ts.Identifier = callExpression.expression as ts.Identifier; - const nextBlockNode: ts.Block = getNextNode(node); - if (SINGLE_CHILD_COMPONENT.has(nodeName.escapedText.toString())) { - if (!nextBlockNode) { - return false; - } - if (nextBlockNode && nextBlockNode.statements) { - const length: number = nextBlockNode.statements.length; - if (!length) { - return false; - } - if (length > 3) { - return true; - } - const childCount: number = getBlockChildrenCount(nextBlockNode, allComponentNames); - if (childCount > 1) { - return true; - } - } - } - return false; -} - -function getBlockChildrenCount(blockNode: ts.Block, allComponentNames: Set): 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.isBlock(item)) { - maxCount += getBlockChildrenCount(item, allComponentNames); - } - if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) && - !isForEachComponent(item.expression) && isComponent(item.expression, allComponentNames)) { - maxCount += 1; - if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) { - ++i; - } - } - if (maxCount > 1) { - break; - } - } - return maxCount; -} - -function isComponent(node: ts.CallExpression, allComponentNames: Set): 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): number { - const maxCount: number = - Math.max(getStatementCount(ifNode.thenStatement, allComponentNames), - getStatementCount(ifNode.elseStatement, allComponentNames)); - return maxCount; -} - -function getStatementCount(node: ts.Node, allComponentNames: Set): 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.isCallExpression(node.expression) && - isForEachComponent(node.expression)) { - maxCount = 2; - } else if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression) && - !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames)) { - maxCount = 1; - } - return maxCount; -} - -function checkSpecificChildComponent(node: ts.Node, allComponentNames: Set, - 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): boolean { - const callExpression: ts.CallExpression = node.expression as ts.CallExpression; - const nodeName: ts.Identifier = callExpression.expression as ts.Identifier; - const nodeNameString: string = nodeName.escapedText.toString(); - const blockNode: ts.Block = getNextNode(node); - let isNonspecific: boolean = false; - if (SPECIFIC_CHILD_COMPONENT.has(nodeNameString) && blockNode) { - const specificChildSet: Set = SPECIFIC_CHILD_COMPONENT.get(nodeNameString); - isNonspecific = isNonspecificChildBlock(blockNode, specificChildSet, allComponentNames); - if (isNonspecific) { - return isNonspecific; - } - } - return isNonspecific; -} - -function isNonspecificChildBlock(blockNode: ts.Block, specificChildSet: Set, - allComponentNames: Set): 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.isCallExpression(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.isCallExpression(item.expression) && - !isForEachComponent(item.expression) && isComponent(item.expression, allComponentNames)) { - const isNonspecific: boolean = - isNonspecificChildNonForEach(item.expression, specificChildSet); - if (isNonspecific) { - return isNonspecific; - } - if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) { - ++i; - } - } - } - } - return false; -} - -function isNonspecificChildIf(node: ts.IfStatement, specificChildSet: Set, - allComponentNames: Set): boolean { - return isNonspecificChildIfStatement(node.thenStatement, specificChildSet, allComponentNames) || - isNonspecificChildIfStatement(node.elseStatement, specificChildSet, allComponentNames); -} - -function isNonspecificChildForEach(node: ts.CallExpression, specificChildSet: Set, - allComponentNames: Set): boolean { - if (ts.isCallExpression(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.CallExpression | ts.IfStatement = - arrowFunction.body as ts.Block | ts.CallExpression | 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.isCallExpression(body) && !isForEachComponent(body) && - isComponent(body, allComponentNames) && - isNonspecificChildNonForEach(body, specificChildSet)) { - return true; - } - } - return false; -} - -function isNonspecificChildNonForEach(node: ts.CallExpression, - specificChildSet: Set): boolean { - if (ts.isIdentifier(node.expression) && - !specificChildSet.has(node.expression.escapedText.toString())) { - return true; - } - return false; -} - -function isNonspecificChildIfStatement(node: ts.Node, specificChildSet: Set, - allComponentNames: Set): 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.isCallExpression(node.expression) && - isForEachComponent(node.expression) && - isNonspecificChildForEach(node.expression, specificChildSet, allComponentNames)) { - return true; - } - if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression) && - !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames) && - isNonspecificChildNonForEach(node.expression, specificChildSet)) { - return true; - } - return false; -} - -function collectComponentProps(node: ts.ClassDeclaration): void { - const componentName: string = node.name.getText(); - const ComponentSet: IComponentSet = getComponentSet(node); - propertyCollection.set(componentName, ComponentSet.propertys); - 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); -} - -export function getComponentSet(node: ts.ClassDeclaration): IComponentSet { - const propertys: Set = new Set(); - const states: Set = new Set(); - const links: Set = new Set(); - const props: Set = new Set(); - const regulars: Set = new Set(); - const storageProps: Set = new Set(); - const storageLinks: Set = new Set(); - const provides: Set = new Set(); - const consumes: Set = new Set(); - const objectLinks: Set = new Set(); - traversalComponentProps(node, propertys, regulars, states, links, props, storageProps, - storageLinks, provides, consumes, objectLinks); - return { - propertys, regulars, states, links, props, storageProps, storageLinks, provides, - consumes, objectLinks - }; -} - -function traversalComponentProps(node: ts.ClassDeclaration, propertys: Set, - regulars: Set, states: Set, links: Set, props: Set, - storageProps: Set, storageLinks: Set, provides: Set, - consumes: Set, objectLinks: Set): void { - let isStatic: boolean = true; - if (node.members) { - const currentMethodCollection: Set = new Set(); - node.members.forEach(item => { - if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { - const propertyName: string = item.name.getText(); - propertys.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(decoratorName, propertyName, states, links, props, storageProps, - storageLinks, provides, consumes, objectLinks); - } - } - } - } - 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(decorator: string, name: string, states: Set, links: Set, - props: Set, storageProps: Set, storageLinks: Set, provides: Set, - consumes: Set, objectLinks: Set): 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; - } -} - -export function sourceReplace(source: string): string { - let content: string = source; - - // replace struct->class - content = content.replace( - new RegExp('\\b' + STRUCT + '\\b.+\\{', 'g'), item => { - item = item.replace(new RegExp('\\b' + STRUCT + '\\b', 'g'), `${CLASS} `); - return `${item} constructor(${COMPONENT_CONSTRUCTOR_ID}?, ${COMPONENT_CONSTRUCTOR_PARENT}?, ${COMPONENT_CONSTRUCTOR_PARAMS}?) {}`; - }); - - content = preprocessExtend(content); - content = preprocessNewExtend(content); - // process @system. - content = processSystemApi(content); - - return content; -} - -export function preprocessExtend(content: string): string { - const REG_EXTEND: RegExp = /(? { - collectExtend(item3, item4, item6); - return `${item1}${COMPONENT_EXTEND_DECORATOR} function${item2}__${item3}__${item4}${item5}${item6}${item7}${item3}`; - }); -} - -export function preprocessNewExtend(content: string): string { - const REG_EXTEND: RegExp = /(? { - collectExtend(item2, item5, item7); - return `${item1}${COMPONENT_EXTEND_DECORATOR}${item3}function${item4}__${item2}__${item5}${item6}${item7}${item8}${item2}`; - }); -} - -function collectExtend(component: string, attribute: string, parameter: string): void { - let parameterCount: number; - if (parameter) { - parameterCount = parameter.split(',').length; - } else { - parameterCount = 0; - } - BUILDIN_STYLE_NAMES.add(attribute); - if (EXTEND_ATTRIBUTE.has(component)) { - EXTEND_ATTRIBUTE.get(component).add({ attribute, parameterCount }); - } else { - EXTEND_ATTRIBUTE.set(component, new Set([{ attribute, parameterCount }])); - } -} - -export function processSystemApi(content: string): string { - const REG_SYSTEM: RegExp = - /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; - return 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) => { - const moduleType: string = item2 || item5; - const systemKey: string = item3 || item6; - const systemValue: string = item1 || item4; - if (NATIVE_MODULE.has(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} = isSystemplugin('${systemKey}', '${OHOS_PLUGIN}') ? ` + - `globalThis.ohosplugin.${systemKey} : isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ? ` + - `globalThis.systemplugin.${systemKey} : globalThis.requireNapi('${systemKey}')`; - } - return item; - }); -} - -export function resetComponentCollection() { - componentCollection.entryComponent = null; - componentCollection.previewComponent = null; -} +/* + * 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, + STRUCT, + CLASS, + 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_CONSTRUCTOR_ID, + COMPONENT_CONSTRUCTOR_PARENT, + COMPONENT_CONSTRUCTOR_PARAMS, + COMPONENT_EXTEND_DECORATOR, + COMPONENT_OBSERVED_DECORATOR +} from './pre_define'; +import { + INNER_COMPONENT_NAMES, + AUTOMIC_COMPONENT, + SINGLE_CHILD_COMPONENT, + SPECIFIC_CHILD_COMPONENT, + BUILDIN_STYLE_NAMES, + EXTEND_ATTRIBUTE +} from './component_map'; +import { + LogType, + LogInfo, + componentInfo, + addLog, + hasDecorator +} from './utils'; + +export interface ComponentCollection { + entryComponent: string; + previewComponent: string; + customDialogs: Set; + customComponents: Set; + currentClassName: string; +} + +export interface IComponentSet { + propertys: Set; + regulars: Set; + states: Set; + links: Set; + props: Set; + storageProps: Set; + storageLinks: Set; + provides: Set; + consumes: Set; + objectLinks: Set; +} + +export const componentCollection: ComponentCollection = { + entryComponent: null, + previewComponent: null, + customDialogs: new Set([]), + customComponents: new Set([]), + currentClassName: null +}; + +export const observedClassCollection: Set = new Set(); +export const enumCollection: Set = new Set(); +export const classMethodCollection: Map> = new Map(); +export const dollarCollection: Set = new Set(); + +export const propertyCollection: Map> = new Map(); +export const stateCollection: Map> = new Map(); +export const linkCollection: Map> = new Map(); +export const propCollection: Map> = new Map(); +export const regularCollection: Map> = new Map(); +export const storagePropCollection: Map> = new Map(); +export const storageLinkCollection: Map> = new Map(); +export const provideCollection: Map> = new Map(); +export const consumeCollection: Map> = new Map(); +export const objectLinkCollection: Map> = new Map(); + +export const isStaticViewCollection: Map = new Map(); + +export const moduleCollection: Set = 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 = + 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.TS); + 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 (isStruct(item)) { + if (index + 1 < arr.length && ts.isExpressionStatement(arr[index + 1]) && + // @ts-ignore + arr[index + 1].expression && ts.isIdentifier(arr[index + 1].expression)) { + if (ts.isExportAssignment(item) && hasComponentDecorator(item)) { + checkDecorators(item, result, arr[index + 1] as ts.ExpressionStatement, log, sourceFile); + } else if (index > 0 && hasComponentDecorator(arr[index - 1])) { + checkDecorators(arr[index - 1] as ts.MissingDeclaration, result, + arr[index + 1] as ts.ExpressionStatement, log, sourceFile); + } else { + // @ts-ignore + const pos: number = item.expression.getStart(); + const message: string = `A struct should use decorator '@Component'.`; + addLog(LogType.WARN, message, pos, log, sourceFile); + } + } else { + // @ts-ignore + const pos: number = item.expression.getStart(); + const message: string = `A struct must have a name.`; + addLog(LogType.ERROR, message, pos, log, sourceFile); + } + } + if (ts.isMissingDeclaration(item) && /struct/.test(item.getText())) { + const message: string = `Please use a valid decorator.`; + addLog(LogType.ERROR, message, item.getStart(), log, sourceFile); + } + }); + validateEntryCount(result, fileQuery, sourceFile.fileName, log); + validatePreviewCount(result, sourceFile.fileName, log); + } + + return log.length ? log : null; +} + +function validateEntryCount(result: DecoratorResult, fileQuery: string, + fileName: string, log: LogInfo[]): void { + if (result.entryCount !== 1 && fileQuery === '?entry') { + log.push({ + type: LogType.ERROR, + message: `A page must have one and only one '@Entry' decorator with a struct.`, + fileName: fileName + }); + } +} + +function validatePreviewCount(result: DecoratorResult, fileName: string, log: LogInfo[]): void { + if (result.previewCount > 1) { + log.push({ + type: LogType.ERROR, + message: `A page can have at most one '@Preview' decorator with a struct.`, + 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; +} + +function isStruct(node: ts.Node): boolean { + if ((ts.isExpressionStatement(node) || ts.isExportAssignment(node)) && + node.expression && ts.isIdentifier(node.expression) && node.expression.getText() === STRUCT) { + return true; + } + return false; +} + +function hasComponentDecorator(node: ts.Node): boolean { + if ((ts.isMissingDeclaration(node) || ts.isExportAssignment(node)) && + node.decorators && node.decorators.length) { + return true; + } + return false; +} + +interface DecoratorResult { + entryCount: number; + previewCount: number; +} + +function checkDecorators(node: ts.MissingDeclaration | ts.ExportAssignment, result: DecoratorResult, + component: ts.ExpressionStatement, log: LogInfo[], sourceFile: ts.SourceFile): void { + let hasComponentDecorator: boolean = false; + const componentName: string = component.getText(); + node.decorators.forEach((element) => { + const name: string = element.getText(); + if (INNER_COMPONENT_DECORATORS.has(name)) { + componentCollection.customComponents.add(componentName); + switch (name) { + case COMPONENT_DECORATOR_ENTRY: + result.entryCount++; + componentCollection.entryComponent = componentName; + 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); + } +} + +function checkUISyntax(filePath: string, allComponentNames: Set, content: string, + log: LogInfo[]): void { + const sourceFile: ts.SourceFile = ts.createSourceFile(filePath, content, + ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + visitAllNode(sourceFile, sourceFile, allComponentNames, log); +} + +function visitAllNode(node: ts.Node, sourceFileNode: ts.SourceFile, allComponentNames: Set, + log: LogInfo[]) { + checkAllNode(node, allComponentNames, sourceFileNode, log); + if (ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name)) { + collectComponentProps(node); + } + node.getChildren().forEach((item: ts.Node) => visitAllNode(item, sourceFileNode, allComponentNames, log)); +} + +function checkAllNode(node: ts.Node, allComponentNames: Set, 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.isCallExpression(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 callExpression: ts.CallExpression = node.expression as ts.CallExpression; + const nodeName: ts.Identifier = callExpression.expression as ts.Identifier; + if (AUTOMIC_COMPONENT.has(nodeName.escapedText.toString()) && getNextNode(node)) { + return true; + } + return false; +} + +function getNextNode(node: ts.Node): ts.Block { + if (node.parent && ts.isBlock(node.parent) && node.parent.statements) { + const statementsArray: ts.Node[] = Array.from(node.parent.statements); + for (let i = 0; i < statementsArray.length - 1; i++) { + const curNode: ts.Node = statementsArray[i]; + const nextNode: ts.Node = statementsArray[i + 1]; + if (node === curNode && ts.isBlock(nextNode)) { + return nextNode; + } + } + } +} + +function checkOneChildComponent(node: ts.Node, allComponentNames: Set, + sourceFileNode: ts.SourceFile, log: LogInfo[]): void { + if (ts.isExpressionStatement(node) && ts.isCallExpression(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): boolean { + const callExpression: ts.CallExpression = node.expression as ts.CallExpression; + const nodeName: ts.Identifier = callExpression.expression as ts.Identifier; + const nextBlockNode: ts.Block = getNextNode(node); + if (SINGLE_CHILD_COMPONENT.has(nodeName.escapedText.toString())) { + if (!nextBlockNode) { + return false; + } + if (nextBlockNode && nextBlockNode.statements) { + const length: number = nextBlockNode.statements.length; + if (!length) { + return false; + } + if (length > 3) { + return true; + } + const childCount: number = getBlockChildrenCount(nextBlockNode, allComponentNames); + if (childCount > 1) { + return true; + } + } + } + return false; +} + +function getBlockChildrenCount(blockNode: ts.Block, allComponentNames: Set): 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.isBlock(item)) { + maxCount += getBlockChildrenCount(item, allComponentNames); + } + if (ts.isExpressionStatement(item) && ts.isCallExpression(item.expression) && + !isForEachComponent(item.expression) && isComponent(item.expression, allComponentNames)) { + maxCount += 1; + if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) { + ++i; + } + } + if (maxCount > 1) { + break; + } + } + return maxCount; +} + +function isComponent(node: ts.CallExpression, allComponentNames: Set): 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): number { + const maxCount: number = + Math.max(getStatementCount(ifNode.thenStatement, allComponentNames), + getStatementCount(ifNode.elseStatement, allComponentNames)); + return maxCount; +} + +function getStatementCount(node: ts.Node, allComponentNames: Set): 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.isCallExpression(node.expression) && + isForEachComponent(node.expression)) { + maxCount = 2; + } else if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression) && + !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames)) { + maxCount = 1; + } + return maxCount; +} + +function checkSpecificChildComponent(node: ts.Node, allComponentNames: Set, + 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): boolean { + const callExpression: ts.CallExpression = node.expression as ts.CallExpression; + const nodeName: ts.Identifier = callExpression.expression as ts.Identifier; + const nodeNameString: string = nodeName.escapedText.toString(); + const blockNode: ts.Block = getNextNode(node); + let isNonspecific: boolean = false; + if (SPECIFIC_CHILD_COMPONENT.has(nodeNameString) && blockNode) { + const specificChildSet: Set = SPECIFIC_CHILD_COMPONENT.get(nodeNameString); + isNonspecific = isNonspecificChildBlock(blockNode, specificChildSet, allComponentNames); + if (isNonspecific) { + return isNonspecific; + } + } + return isNonspecific; +} + +function isNonspecificChildBlock(blockNode: ts.Block, specificChildSet: Set, + allComponentNames: Set): 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.isCallExpression(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.isCallExpression(item.expression) && + !isForEachComponent(item.expression) && isComponent(item.expression, allComponentNames)) { + const isNonspecific: boolean = + isNonspecificChildNonForEach(item.expression, specificChildSet); + if (isNonspecific) { + return isNonspecific; + } + if (i + 1 < length && ts.isBlock(blockNode.statements[i + 1])) { + ++i; + } + } + } + } + return false; +} + +function isNonspecificChildIf(node: ts.IfStatement, specificChildSet: Set, + allComponentNames: Set): boolean { + return isNonspecificChildIfStatement(node.thenStatement, specificChildSet, allComponentNames) || + isNonspecificChildIfStatement(node.elseStatement, specificChildSet, allComponentNames); +} + +function isNonspecificChildForEach(node: ts.CallExpression, specificChildSet: Set, + allComponentNames: Set): boolean { + if (ts.isCallExpression(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.CallExpression | ts.IfStatement = + arrowFunction.body as ts.Block | ts.CallExpression | 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.isCallExpression(body) && !isForEachComponent(body) && + isComponent(body, allComponentNames) && + isNonspecificChildNonForEach(body, specificChildSet)) { + return true; + } + } + return false; +} + +function isNonspecificChildNonForEach(node: ts.CallExpression, + specificChildSet: Set): boolean { + if (ts.isIdentifier(node.expression) && + !specificChildSet.has(node.expression.escapedText.toString())) { + return true; + } + return false; +} + +function isNonspecificChildIfStatement(node: ts.Node, specificChildSet: Set, + allComponentNames: Set): 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.isCallExpression(node.expression) && + isForEachComponent(node.expression) && + isNonspecificChildForEach(node.expression, specificChildSet, allComponentNames)) { + return true; + } + if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression) && + !isForEachComponent(node.expression) && isComponent(node.expression, allComponentNames) && + isNonspecificChildNonForEach(node.expression, specificChildSet)) { + return true; + } + return false; +} + +function collectComponentProps(node: ts.ClassDeclaration): void { + const componentName: string = node.name.getText(); + const ComponentSet: IComponentSet = getComponentSet(node); + propertyCollection.set(componentName, ComponentSet.propertys); + 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); +} + +export function getComponentSet(node: ts.ClassDeclaration): IComponentSet { + const propertys: Set = new Set(); + const states: Set = new Set(); + const links: Set = new Set(); + const props: Set = new Set(); + const regulars: Set = new Set(); + const storageProps: Set = new Set(); + const storageLinks: Set = new Set(); + const provides: Set = new Set(); + const consumes: Set = new Set(); + const objectLinks: Set = new Set(); + traversalComponentProps(node, propertys, regulars, states, links, props, storageProps, + storageLinks, provides, consumes, objectLinks); + return { + propertys, regulars, states, links, props, storageProps, storageLinks, provides, + consumes, objectLinks + }; +} + +function traversalComponentProps(node: ts.ClassDeclaration, propertys: Set, + regulars: Set, states: Set, links: Set, props: Set, + storageProps: Set, storageLinks: Set, provides: Set, + consumes: Set, objectLinks: Set): void { + let isStatic: boolean = true; + if (node.members) { + const currentMethodCollection: Set = new Set(); + node.members.forEach(item => { + if (ts.isPropertyDeclaration(item) && ts.isIdentifier(item.name)) { + const propertyName: string = item.name.getText(); + propertys.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(decoratorName, propertyName, states, links, props, storageProps, + storageLinks, provides, consumes, objectLinks); + } + } + } + } + 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(decorator: string, name: string, states: Set, links: Set, + props: Set, storageProps: Set, storageLinks: Set, provides: Set, + consumes: Set, objectLinks: Set): 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; + } +} + +export function sourceReplace(source: string): string { + let content: string = source; + + // replace struct->class + content = content.replace( + new RegExp('\\b' + STRUCT + '\\b.+\\{', 'g'), item => { + item = item.replace(new RegExp('\\b' + STRUCT + '\\b', 'g'), `${CLASS} `); + return `${item} constructor(${COMPONENT_CONSTRUCTOR_ID}?, ${COMPONENT_CONSTRUCTOR_PARENT}?, ${COMPONENT_CONSTRUCTOR_PARAMS}?) {}`; + }); + + content = preprocessExtend(content); + content = preprocessNewExtend(content); + // process @system. + content = processSystemApi(content); + + return content; +} + +export function preprocessExtend(content: string): string { + const REG_EXTEND: RegExp = /(? { + collectExtend(item3, item4, item6); + return `${item1}${COMPONENT_EXTEND_DECORATOR} function${item2}__${item3}__${item4}${item5}${item6}${item7}${item3}`; + }); +} + +export function preprocessNewExtend(content: string): string { + const REG_EXTEND: RegExp = /(? { + collectExtend(item2, item5, item7); + return `${item1}${COMPONENT_EXTEND_DECORATOR}${item3}function${item4}__${item2}__${item5}${item6}${item7}${item8}${item2}`; + }); +} + +function collectExtend(component: string, attribute: string, parameter: string): void { + let parameterCount: number; + if (parameter) { + parameterCount = parameter.split(',').length; + } else { + parameterCount = 0; + } + BUILDIN_STYLE_NAMES.add(attribute); + if (EXTEND_ATTRIBUTE.has(component)) { + EXTEND_ATTRIBUTE.get(component).add({ attribute, parameterCount }); + } else { + EXTEND_ATTRIBUTE.set(component, new Set([{ attribute, parameterCount }])); + } +} + +export function processSystemApi(content: string): string { + const REG_SYSTEM: RegExp = + /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; + return 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) => { + const moduleType: string = item2 || item5; + const systemKey: string = item3 || item6; + const systemValue: string = item1 || item4; + 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} = isSystemplugin('${systemKey}', '${OHOS_PLUGIN}') ? ` + + `globalThis.ohosplugin.${systemKey} : isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ? ` + + `globalThis.systemplugin.${systemKey} : globalThis.requireNapi('${systemKey}')`; + } + return item; + }); +} + +export function resetComponentCollection() { + componentCollection.entryComponent = null; + componentCollection.previewComponent = null; +} diff --git a/compiler/test/test.ts b/compiler/test/test.ts index 5ada0239a6ffa39d763245ea5151e0a4b3d082de..a4254e7d8bad35f9101ab221a4158999dc0ab03f 100644 --- a/compiler/test/test.ts +++ b/compiler/test/test.ts @@ -46,7 +46,7 @@ function expectActual(name: string, filePath: string) { "target": ts.ScriptTarget.ES2017 }, fileName: `${name}.ts`, - transformers: { before: [processUISyntax(null)] } + transformers: { before: [processUISyntax(null, true)] } }); componentInfo.id = 0; componentCollection.customComponents.clear(); diff --git a/compiler/test/ut/component/customComponent.ts b/compiler/test/ut/component/customComponent.ts index 8a8fd15169de39fbe80d7b599a9bd8aa193bb8c6..6907d09b70e53de27e27748abffa06eaefa9e3f1 100644 --- a/compiler/test/ut/component/customComponent.ts +++ b/compiler/test/ut/component/customComponent.ts @@ -91,7 +91,8 @@ export const expectResult: string = if (earlierCreatedChild_2 == undefined) { View.create(new Child("2", this, { stateProperty: this.regularToState, propProperty: this.stateToProp, - regularProperty: this.regularToRegular, linkProperty: this.__stateToLink + regularProperty: this.regularToRegular, + linkProperty: this.__stateToLink })); } else { diff --git a/compiler/webpack.config.js b/compiler/webpack.config.js index 1a267d47ccaf547bb18afcc1514cff75b861007e..e4c649afae760441374c88f0ce7d6be4d8afa387 100644 --- a/compiler/webpack.config.js +++ b/compiler/webpack.config.js @@ -53,7 +53,7 @@ function initConfig(config) { module: { rules: [ { - test: /\.ets$/, + test: /\.(ets|ts)$/, use: [ { loader: path.resolve(__dirname, 'lib/result_process.js') }, { @@ -64,7 +64,7 @@ function initConfig(config) { configFile: path.resolve(__dirname, 'tsconfig.json'), getCustomTransformers(program) { return { - before: [processUISyntax(program, path.join(projectPath, 'pages'))], + before: [processUISyntax(program)], after: [] }; }, @@ -74,26 +74,10 @@ function initConfig(config) { { loader: path.resolve(__dirname, 'lib/pre_process.js') } ] }, - { - test: /\.ts$/, - exclude: /node_modules/, - use: [ - { loader: path.resolve(__dirname, 'lib/process_ability_entry_file.js') }, - { - loader: 'ts-loader', - options: { - onlyCompileBundledFiles: true, - configFile: path.resolve(__dirname, 'tsconfig.json'), - } - }, - { loader: path.resolve(__dirname, 'lib/process_worker.js') } - ] - }, { test: /\.js$/, - exclude: /node_modules/, use: [ - { loader: path.resolve(__dirname, 'lib/process_worker.js') } + { loader: path.resolve(__dirname, 'lib/process_system_module.js') } ] } ] @@ -102,7 +86,7 @@ function initConfig(config) { global: false }, resolve: { - extensions:['.ts', '.ets', '.js'], + extensions:['.js', '.ets', '.ts'], modules: [ projectPath, path.join(projectPath, '../../../../../'),