Merge branch fix/material-parser-typescript into release/1.0.0

Title: fix: fix typescript related bugs, including the following: 

fix: fix typescript related bugs, including the following:
1. fix bug of failing to resolve RFC components
2. support transforming function args
3. fix bug of oneOfType
4. fix bug of crash when circular parsing
5. fix bug of union
6. support tuple
7. fix bug of builtin type parsing
8. fix bug of false positive component identification
9. fix bug of entry resolving

Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3883758
This commit is contained in:
gengyang.gy 2020-10-13 10:28:06 +08:00
commit 2c7dbe37f1
22 changed files with 52848 additions and 32092 deletions

View File

@ -0,0 +1,9 @@
module.exports = {
extends: ['eslint-config-ali/typescript/react'],
rules: {
'implicit-arrow-linebreak': 1,
'@typescript-eslint/indent': 1,
'function-paren-newline': 1,
'no-bitwise': 0,
},
};

View File

@ -42,13 +42,13 @@ export interface Npm {
[k: string]: any;
}
export interface PropsSection {
props: Array<{
props: {
name: string;
propType: PropType;
description?: string;
defaultValue?: any;
[k: string]: any;
}>;
}[];
[k: string]: any;
}
export interface RequiredType {
@ -57,7 +57,7 @@ export interface RequiredType {
}
export interface OneOf {
type: 'oneOf';
value: Array<string | number | boolean>;
value: (string | number | boolean)[];
isRequired?: boolean;
[k: string]: any;
}
@ -81,19 +81,19 @@ export interface ObjectOf {
}
export interface Shape {
type: 'shape';
value: Array<{
value: {
name?: string;
propType?: PropType;
}>;
}[];
isRequired?: boolean;
[k: string]: any;
}
export interface Exact {
type: 'exact';
value: Array<{
value: {
name?: string;
propType?: PropType;
}>;
}[];
isRequired?: boolean;
[k: string]: any;
}
@ -119,24 +119,24 @@ export interface ConfigureFieldProp {
}
export interface ConfigureFieldSetter {
componentName:
| 'List'
| 'Object'
| 'Function'
| 'Node'
| 'Mixin'
| 'Expression'
| 'Switch'
| 'Number'
| 'Input'
| 'TextArea'
| 'Date'
| 'DateYear'
| 'DateMonth'
| 'DateRange'
| 'ColorPicker'
| 'CodeEditor'
| 'Select'
| 'RadioGroup';
| 'List'
| 'Object'
| 'Function'
| 'Node'
| 'Mixin'
| 'Expression'
| 'Switch'
| 'Number'
| 'Input'
| 'TextArea'
| 'Date'
| 'DateYear'
| 'DateMonth'
| 'DateRange'
| 'ColorPicker'
| 'CodeEditor'
| 'Select'
| 'RadioGroup';
props?: {
[k: string]: any;
};

View File

@ -1,4 +1,4 @@
import parseDynamic from './runtime';
import parseDynamic from './dynamic';
import parseJS from './js';
import parseTS from './ts';
import { install, installPeerDeps, installTypeModules } from '../utils';
@ -24,9 +24,9 @@ export default async (args: IParseArgs) => {
moduleFileAbsolutePath = mainFileAbsolutePath,
} = args;
if (args.accesser === 'local') {
if (moduleFileAbsolutePath.endsWith('ts') || moduleFileAbsolutePath.endsWith('tsx')) {
if (mainFileAbsolutePath.endsWith('ts') || mainFileAbsolutePath.endsWith('tsx')) {
await install(args);
return parseTS(moduleFileAbsolutePath);
return parseTS(mainFileAbsolutePath);
} else {
try {
return parseJS(moduleFileAbsolutePath);

View File

@ -1,4 +1,4 @@
import { omit, pick, isNil } from 'lodash';
import { omit, pick, isNil, uniq } from 'lodash';
import { safeEval, isEvaluable } from '../utils';
import { debug } from '../core';
@ -6,10 +6,7 @@ const log = debug.extend('parse:transform');
export function transformType(itemType: any) {
if (typeof itemType === 'string') return itemType;
const { name, elements, value = elements, computed, required, type } = itemType;
// if (!value && !required && !type) {
// return name;
// }
const { name, elements, value = elements, computed, required, type, raw } = itemType;
if (computed !== undefined && value) {
return safeEval(value);
}
@ -24,7 +21,6 @@ export function transformType(itemType: any) {
case 'string':
case 'bool':
case 'any':
case 'func':
case 'symbol':
case 'object':
case 'null':
@ -32,21 +28,34 @@ export function transformType(itemType: any) {
case 'element':
case 'node':
break;
case 'func':
if (value) {
result.value = value.map(x => ({
...x,
propType: transformType(x.propType),
}));
result.raw = raw;
}
break;
case 'literal':
return safeEval(value);
result.type = 'oneOf';
result.value = [safeEval(value)];
break;
case 'enum':
case 'tuple':
case 'oneOf':
result.type = 'oneOf';
result.value = value.map(transformType);
break;
case 'tuple':
result.type = 'tuple';
result.value = value.map(transformType);
break;
case 'union': {
const { raw } = itemType;
if (raw) {
if (raw.match(/ReactNode$/)) {
if (itemType.raw) {
if (itemType.raw.match(/ReactNode$/)) {
result.type = 'node';
break;
} else if (raw.match(/Element$/)) {
} else if (itemType.raw.match(/Element$/)) {
result.type = 'element';
break;
}
@ -69,12 +78,12 @@ export function transformType(itemType: any) {
case 'Array':
case 'arrayOf': {
result.type = 'arrayOf';
const v = omit(transformType(value[0]), ['isRequired']);
if (Object.keys(v).length === 1 && v.type) {
result.value = v.type;
} else {
result.value = v;
let _itemType = transformType(value[0]);
if (typeof _itemType === 'object') {
_itemType = omit(_itemType, ['isRequired']);
}
result.value = _itemType;
break;
}
case 'signature': {
@ -83,13 +92,25 @@ export function transformType(itemType: any) {
break;
}
result.type = 'shape';
const properties = type?.signature?.properties || [];
const properties = type?.signature?.properties || itemType?.signature?.properties || [];
if (properties.length === 0) {
result.type = 'object';
if (raw?.includes('=>')) {
result.type = 'func';
result.raw = raw;
} else {
result.type = 'object';
}
} else if (properties.length === 1 && typeof properties[0].key === 'object') {
result.type = 'objectOf';
const v = transformType(properties[0].value);
if (typeof v.type === 'string') result.value = v.type;
if (typeof v === 'string') {
result.value = v;
result.type = 'objectOf';
} else if (typeof v?.type === 'string') {
result.value = v.type;
result.type = 'objectOf';
} else {
result.type = 'object';
}
} else if (properties.length === 1 && properties[0].key === '__call') {
result.type = 'func';
} else {
@ -97,10 +118,15 @@ export function transformType(itemType: any) {
.filter((item: any) => typeof item.key !== 'object')
.map((prop: any) => {
const { key } = prop;
return transformItem(key, {
const typeItem = {
...omit(prop.value, 'name'),
type: pick(prop.value, ['name', 'value']),
});
type: prop.value.type || {},
};
typeItem.type = {
...typeItem.type,
...pick(prop.value, ['name', 'value']),
};
return transformItem(key, typeItem);
});
}
break;
@ -111,7 +137,7 @@ export function transformType(itemType: any) {
break;
case 'exact':
case 'shape':
result.value = Object.keys(value).map((n) => {
result.value = Object.keys(value).map(n => {
// tslint:disable-next-line:variable-name
const { name: _name, ...others } = value[n];
return transformItem(n, {
@ -125,21 +151,71 @@ export function transformType(itemType: any) {
case (name.match(/ReactNode$/) || {}).input:
result.type = 'node';
break;
case (name.match(/Element$/) || {}).input:
case (name.match(/JSX\.Element$/) || {}).input:
result.type = 'element';
break;
// case (name.match(/ElementType$/) || {}).input:
// result.type = 'elementType';
// break;
default:
// result.type = 'instanceOf';
// result.value = name;
result.type = 'any';
result.type = 'object';
break;
}
if (Object.keys(result).length === 1) {
return result.type;
}
if (result?.type === 'oneOfType') {
return combineOneOfValues(result);
}
return result;
}
function combineOneOfValues(propType) {
if (propType.type !== 'oneOfType') {
return propType;
}
const newValue = [];
let oneOfItem = null;
let firstBooleanIndex = -1;
propType.value.forEach(item => {
if (item?.type === 'oneOf') {
if (!oneOfItem) {
oneOfItem = {
type: 'oneOf',
value: [],
};
}
if (item.value.includes(true) || item.value.includes(false)) {
if (firstBooleanIndex !== -1) {
oneOfItem.value.splice(firstBooleanIndex, 1);
newValue.push('bool');
} else {
firstBooleanIndex = oneOfItem.value.length;
oneOfItem.value = oneOfItem.value.concat(item.value);
}
} else {
oneOfItem.value = oneOfItem.value.concat(item.value);
}
} else {
newValue.push(item);
}
});
let result = propType;
const oneOfItemLength = oneOfItem?.value?.length;
if (oneOfItemLength) {
newValue.push(oneOfItem);
}
if (firstBooleanIndex !== -1 || oneOfItemLength) {
result = {
...propType,
value: newValue,
};
}
if (result.value.length === 1 && result.value[0]?.type === 'oneOf') {
result = {
...result,
type: 'oneOf',
value: result.value[0].value,
};
}
result.value = uniq(result.value);
return result;
}
@ -175,6 +251,8 @@ export function transformItem(name: string, item: any) {
if (defaultValue === null) {
result.defaultValue = defaultValue;
} else {
// if ('computed' in defaultValue) {
// val = val.value;
try {
const value = safeEval(defaultValue.value);
if (isEvaluable(value)) {
@ -184,9 +262,13 @@ export function transformItem(name: string, item: any) {
log(e);
}
}
// else {
// result.defaultValue = defaultValue.value;
// }
}
if (result.propType === undefined) {
delete result.propType;
}
return result;
}

View File

@ -12,9 +12,18 @@ type ExtendedType = ts.Type & {
typeArguments: any[];
};
function getNextParentIds(parentIds: number[], type: ts.Type) {
// @ts-ignore
const id = type?.symbol?.id;
if (id) {
return [...parentIds, id];
}
return parentIds;
}
function getSymbolName(symbol: ts.Symbol) {
// @ts-ignore
const prefix: string = symbol.parent && getSymbolName(symbol.parent);
const prefix: string = symbol?.parent && getSymbolName(symbol.parent);
const name = symbol.getName();
if (prefix && prefix.length <= 20) {
return `${prefix}.${name}`;
@ -22,6 +31,33 @@ function getSymbolName(symbol: ts.Symbol) {
return name;
}
function getFunctionParams(parameters: any[] = [], checker, parentIds, type) {
return parameters.map(node => {
const typeObject = checker.getTypeOfSymbolAtLocation(node.symbol, node.symbol.valueDeclaration);
const v = getDocgenTypeHelper(checker, typeObject, false, getNextParentIds(parentIds, type));
const name = node.symbol.escapedName;
return {
name,
propType: v,
};
});
}
/**
* Indicates that a symbol is an alias that does not merge with a local declaration.
* OR Is a JSContainer which may merge an alias with a local declaration
*/
// function isNonLocalAlias(
// symbol: ts.Symbol | undefined,
// excludes = SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace,
// ): symbol is ts.Symbol {
// if (!symbol) return false;
// return (
// (symbol.flags & (SymbolFlags.Alias | excludes)) === SymbolFlags.Alias ||
// !!(symbol.flags & SymbolFlags.Alias && symbol.flags & SymbolFlags.Assignment)
// );
// }
const blacklistNames = [
'prototype',
'getDerivedStateFromProps',
@ -34,6 +70,38 @@ const blacklistNames = [
'Consumer',
];
const blacklistPatterns = [
/^HTML/,
/^React\./,
/^Object$/,
/^Date$/,
/^Promise$/,
/^XML/,
/^Function$/,
];
function hasTooManyTypes(type) {
return type?.types?.length >= 20;
}
function isComplexType(type) {
let isAliasSymbol = false;
let symbol = type?.symbol;
if (!symbol) {
symbol = type?.aliasSymbol;
isAliasSymbol = true;
}
if (!symbol) return false;
if (isAliasSymbol && !hasTooManyTypes(type)) {
return false;
}
const name = getSymbolName(symbol);
if (blacklistPatterns.some(patt => patt.test(name))) {
return true;
}
return false;
}
function getDocgenTypeHelper(
checker: ts.TypeChecker,
type: ts.Type,
@ -70,10 +138,10 @@ function getDocgenTypeHelper(
function getShapeFromArray(symbolArr: ts.Symbol[], _type: ts.Type) {
const shape: Array<{
key:
| {
name: string;
}
| string;
| {
name: string;
}
| string;
value: any;
}> = symbolArr.map(prop => {
const propType = checker.getTypeOfSymbolAtLocation(
@ -89,7 +157,7 @@ function getDocgenTypeHelper(
propType,
false,
// @ts-ignore
[...parentIds, _type.id],
getNextParentIds(parentIds, _type),
// @ts-ignore
prop?.valueDeclaration?.questionToken ? false : undefined,
),
@ -108,11 +176,13 @@ function getDocgenTypeHelper(
key: {
name: 'string',
},
// @ts-ignore use internal methods
value: getDocgenTypeHelper(checker, _type.stringIndexInfo.type, false, [
...parentIds,
(_type as ExtendedType).id,
]),
value: getDocgenTypeHelper(
checker,
// @ts-ignore use internal methods
_type.stringIndexInfo.type,
false,
getNextParentIds(parentIds, _type),
),
});
} else if (_type.getNumberIndexType()) {
// @ts-ignore use internal methods
@ -124,11 +194,13 @@ function getDocgenTypeHelper(
name: 'number',
},
// @ts-ignore use internal methods
value: getDocgenTypeHelper(checker, _type.numberIndexInfo.type, false, [
...parentIds,
(_type as ExtendedType).id,
]),
value: getDocgenTypeHelper(
checker,
// @ts-ignore use internal methods
_type.numberIndexInfo.type,
false,
getNextParentIds(parentIds, _type),
),
});
}
return shape;
@ -139,6 +211,9 @@ function getDocgenTypeHelper(
if (symbol && symbol.members) {
// @ts-ignore
const props: ts.Symbol[] = Array.from(symbol.members.values());
if (props.length >= 20) {
throw new Error('too many props');
}
return getShapeFromArray(
props.filter(prop => prop.getName() !== '__index'),
_type,
@ -147,6 +222,9 @@ function getDocgenTypeHelper(
// @ts-ignore
const args = _type.resolvedTypeArguments || [];
const props = checker.getPropertiesOfType(_type);
if (props.length >= 20) {
throw new Error('too many props');
}
const shape = getShapeFromArray(props.slice(0, args.length), _type);
return shape;
}
@ -154,9 +232,9 @@ function getDocgenTypeHelper(
const pattern = /^__global\.(.+)$/;
// @ts-ignore
if (parentIds.includes(type.id)) {
if (parentIds.includes(type?.symbol?.id)) {
return makeResult({
name: checker.typeToString(type),
name: 'object', // checker.typeToString(type),
});
}
if (type.symbol) {
@ -170,6 +248,7 @@ function getDocgenTypeHelper(
}
}
}
if (type.flags & TypeFlags.Number) {
return makeResult({
name: 'number',
@ -198,63 +277,140 @@ function getDocgenTypeHelper(
return makeResult({
name: 'any',
});
} else if (type.flags & TypeFlags.Union) {
} else if (type.flags & TypeFlags.Union && !isComplexType(type)) {
return makeResult({
name: 'union',
// @ts-ignore
value: type.types.map(t => getDocgenTypeHelper(checker, t, true, [...parentIds, type.id])),
value: type.types.map(t =>
getDocgenTypeHelper(checker, t, true, getNextParentIds(parentIds, type)),
),
});
} else if (isComplexType(type)) {
return makeResult({
name: getSymbolName(type?.symbol || type?.aliasSymbol),
});
} else if (type.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
if (isTuple(type)) {
const props = getShape(type);
return makeResult({
name: 'union',
value: props.map(p => p.value),
});
try {
const props = getShape(type);
return makeResult({
name: 'tuple',
value: props.map(p => p.value),
});
} catch (e) {
return makeResult({
name: 'object',
});
}
// @ts-ignore
} else if (checker.isArrayType(type)) {
return makeResult({
name: 'Array',
// @ts-ignore
elements: [
getDocgenTypeHelper(checker, (type as ExtendedType).typeArguments[0], false, [
...parentIds,
(type as any).id,
]),
getDocgenTypeHelper(
checker,
(type as ExtendedType).typeArguments[0],
false,
getNextParentIds(parentIds, type),
),
],
});
} else if (type.aliasSymbol) {
return makeResult({
name: getSymbolName(type.aliasSymbol),
});
// @ts-ignore
} else if (type?.symbol?.valueDeclaration?.parameters?.length) {
return makeResult({
name: 'func',
value: getFunctionParams(
// @ts-ignore
type?.symbol?.valueDeclaration?.parameters,
checker,
parentIds,
type,
),
});
} else if (
// @ts-ignore
type?.members?.get('__call')?.declarations[0]?.symbol?.declarations[0]?.parameters?.length
) {
return makeResult({
name: 'func',
value: getFunctionParams(
// @ts-ignore
type?.members?.get('__call')?.declarations[0]?.symbol?.declarations[0]?.parameters,
checker,
parentIds,
type,
),
});
} else {
const props = getShape(type);
return makeResult({
name: 'signature',
type: {
signature: {
properties: props,
try {
const props = getShape(type);
return makeResult({
name: 'signature',
type: {
signature: {
properties: props,
},
},
},
});
});
} catch (e) {
return makeResult({
name: 'object',
});
}
}
} else {
return makeResult({
name: 'any',
name: 'object',
});
}
}
class MyParser extends Parser {
getDocgenType(propType: ts.Type): any {
const parentIds = [];
// @ts-ignore
const result = getDocgenTypeHelper(this.checker, propType, true);
const parentId = propType?.symbol?.parent?.id;
if (parentId) {
parentIds.push(parentId);
}
// @ts-ignore
const result = getDocgenTypeHelper(this.checker, propType, true, parentIds);
return result;
}
// override the builtin method, to avoid the false positive
public extractPropsFromTypeIfStatelessComponent(type: ts.Type): ts.Symbol | null {
const callSignatures = type.getCallSignatures();
if (callSignatures.length) {
// Could be a stateless component. Is a function, so the props object we're interested
// in is the (only) parameter.
for (const sig of callSignatures) {
const params = sig.getParameters();
if (params.length === 0) {
continue;
}
// @ts-ignore
const returnSymbol = this.checker.getReturnTypeOfSignature(sig);
if (!returnSymbol) continue;
const symbol = returnSymbol?.symbol;
if (!symbol) continue;
// @ts-ignore
const typeString = this.checker.symbolToString(symbol);
if (typeString.startsWith('ReactElement')) {
const propsParam = params[0];
if (propsParam) {
return propsParam;
}
}
}
}
return null;
}
}
const compilerOptions = {
@ -300,6 +456,9 @@ export default function parseTS(
if (blacklistNames.includes(name)) {
continue;
}
// polyfill valueDeclaration
sym.valueDeclaration = sym.valueDeclaration || sym.declarations[0];
// @ts-ignore
const info = parser.getComponentInfo(sym, sourceFile, parserOpts.componentNameResolver);
if (info === null) {
@ -326,7 +485,6 @@ export default function parseTS(
sym,
sym.valueDeclaration || sym.declarations[0],
);
Array.prototype.push.apply(
exportSymbols,
type.getProperties().map((x: SymbolWithMeta) => {
@ -341,6 +499,10 @@ export default function parseTS(
const coms = result.reduce((res: any[], info: any) => {
if (!info || !info.props || isEmpty(info.props)) return res;
const props = Object.keys(info.props).reduce((acc: any[], name) => {
// omit aria related properties temporarily
if (name.startsWith('aria-')) {
return acc;
}
try {
const item: any = transformItem(name, info.props[name]);
acc.push(item);

File diff suppressed because it is too large Load Diff

View File

@ -758,7 +758,7 @@ Generated by [AVA](https://avajs.dev).
{
description: '面包屑子节点,需传入 Breadcrumb.Item',
name: 'children',
propType: 'any',
propType: 'object',
},
{
defaultValue: 100,
@ -1066,12 +1066,12 @@ Generated by [AVA](https://avajs.dev).
{
description: '默认选中的日期moment 对象)',
name: 'defaultValue',
propType: 'any',
propType: 'object',
},
{
description: '选中的日期值 (moment 对象)',
name: 'value',
propType: 'any',
propType: 'object',
},
{
name: 'modes',
@ -1186,22 +1186,22 @@ Generated by [AVA](https://avajs.dev).
{
description: '默认的开始日期',
name: 'defaultStartValue',
propType: 'any',
propType: 'object',
},
{
description: '默认的结束日期',
name: 'defaultEndValue',
propType: 'any',
propType: 'object',
},
{
description: '开始日期moment 对象)',
name: 'startValue',
propType: 'any',
propType: 'object',
},
{
description: '结束日期moment 对象)',
name: 'endValue',
propType: 'any',
propType: 'object',
},
{
defaultValue: false,
@ -1391,7 +1391,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'div',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
{
name: 'className',
@ -1423,7 +1423,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'div',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
{
description: '背景图片地址',
@ -1470,7 +1470,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'hr',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
{
description: '分割线是否向内缩进',
@ -1507,7 +1507,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'div',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
{
name: 'className',
@ -1539,7 +1539,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'div',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
{
name: 'className',
@ -2524,12 +2524,12 @@ Generated by [AVA](https://avajs.dev).
{
description: '日期值受控moment 对象',
name: 'value',
propType: 'any',
propType: 'object',
},
{
description: '初始日期值moment 对象',
name: 'defaultValue',
propType: 'any',
propType: 'object',
},
{
defaultValue: 'YYYY-MM-DD',
@ -2711,7 +2711,7 @@ Generated by [AVA](https://avajs.dev).
},
{
name: 'popupComponent',
propType: 'any',
propType: 'object',
},
{
name: 'popupContent',
@ -2996,7 +2996,7 @@ Generated by [AVA](https://avajs.dev).
},
{
name: 'popupComponent',
propType: 'any',
propType: 'object',
},
{
name: 'popupContent',
@ -3059,12 +3059,12 @@ Generated by [AVA](https://avajs.dev).
{
description: '日期值受控moment 对象',
name: 'value',
propType: 'any',
propType: 'object',
},
{
description: '初始日期值moment 对象',
name: 'defaultValue',
propType: 'any',
propType: 'object',
},
{
defaultValue: 'YYYY-MM',
@ -3212,7 +3212,7 @@ Generated by [AVA](https://avajs.dev).
},
{
name: 'popupComponent',
propType: 'any',
propType: 'object',
},
{
name: 'popupContent',
@ -3270,12 +3270,12 @@ Generated by [AVA](https://avajs.dev).
{
description: '日期值受控moment 对象',
name: 'value',
propType: 'any',
propType: 'object',
},
{
description: '初始日期值moment 对象',
name: 'defaultValue',
propType: 'any',
propType: 'object',
},
{
defaultValue: 'YYYY',
@ -3418,7 +3418,7 @@ Generated by [AVA](https://avajs.dev).
},
{
name: 'popupComponent',
propType: 'any',
propType: 'object',
},
{
name: 'popupContent',
@ -3485,12 +3485,12 @@ Generated by [AVA](https://avajs.dev).
{
description: '日期值受控moment 对象',
name: 'value',
propType: 'any',
propType: 'object',
},
{
description: '初始日期值moment 对象',
name: 'defaultValue',
propType: 'any',
propType: 'object',
},
{
defaultValue: 'YYYY-wo',
@ -3639,7 +3639,7 @@ Generated by [AVA](https://avajs.dev).
},
{
name: 'popupComponent',
propType: 'any',
propType: 'object',
},
{
name: 'popupContent',
@ -5011,6 +5011,7 @@ Generated by [AVA](https://avajs.dev).
propType: {
type: 'oneOfType',
value: [
'number',
{
type: 'oneOf',
value: [
@ -5025,7 +5026,6 @@ Generated by [AVA](https://avajs.dev).
'inherit',
],
},
'number',
],
},
},
@ -10159,12 +10159,12 @@ Generated by [AVA](https://avajs.dev).
{
description: '时间值moment 对象或时间字符串,受控状态使用)',
name: 'value',
propType: 'any',
propType: 'object',
},
{
description: '时间初值moment 对象或时间字符串,非受控状态使用)',
name: 'defaultValue',
propType: 'any',
propType: 'object',
},
{
defaultValue: 'medium',
@ -10319,7 +10319,7 @@ Generated by [AVA](https://avajs.dev).
},
{
name: 'popupComponent',
propType: 'any',
propType: 'object',
},
{
name: 'popupContent',
@ -11233,7 +11233,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'article',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
],
screenshot: '',
@ -11261,7 +11261,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'p',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
{
defaultValue: 'long',
@ -11327,7 +11327,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'span',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
{
name: 'children',
@ -12171,6 +12171,7 @@ Generated by [AVA](https://avajs.dev).
propType: {
type: 'oneOfType',
value: [
'number',
{
type: 'oneOf',
value: [
@ -12179,7 +12180,6 @@ Generated by [AVA](https://avajs.dev).
'large',
],
},
'number',
],
},
},
@ -12290,7 +12290,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'div',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
],
screenshot: '',
@ -12341,7 +12341,7 @@ Generated by [AVA](https://avajs.dev).
defaultValue: 'div',
description: '设置标签类型',
name: 'component',
propType: 'any',
propType: 'object',
},
],
screenshot: '',

View File

@ -357,7 +357,7 @@ Generated by [AVA](https://avajs.dev).
},
{
name: 'optionalElementType',
propType: 'any',
propType: 'object',
},
{
name: 'optionalMessage',
@ -475,6 +475,60 @@ Generated by [AVA](https://avajs.dev).
},
]
## ts component 2 by local
> Snapshot 1
[
{
componentName: 'default',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: false,
exportName: 'default',
main: 'src/index.tsx',
package: '@alife/empty',
subName: '',
version: '1.0.1',
},
props: [
{
name: 'className',
propType: 'string',
},
{
name: 'style',
propType: 'object',
},
{
name: 'imageStyle',
propType: 'object',
},
{
name: 'image',
propType: 'node',
},
{
name: 'description',
propType: 'node',
},
{
name: 'type',
propType: {
type: 'oneOf',
value: [
'default',
'custom',
],
},
},
],
screenshot: '',
title: '@alife/empty',
},
]
## ts component by local
> Snapshot 1
@ -493,10 +547,402 @@ Generated by [AVA](https://avajs.dev).
version: '1.0.0',
},
props: [
{
name: 'object',
propType: {
isRequired: true,
type: 'object',
},
},
{
name: 'trigger',
propType: {
type: 'arrayOf',
value: {
type: 'oneOf',
value: [
'click',
'hover',
'contextMenu',
],
},
},
},
{
name: 'str',
propType: 'string',
},
{
name: 'num',
propType: {
isRequired: true,
type: 'number',
},
},
{
name: 'gender',
propType: {
isRequired: true,
type: 'oneOf',
value: [
0,
1,
],
},
},
{
name: 'any',
propType: {
isRequired: true,
type: 'any',
},
},
{
name: 'bool',
propType: {
isRequired: true,
type: 'bool',
},
},
{
name: 'tuple',
propType: {
isRequired: true,
type: 'object',
},
},
{
name: 'enum',
propType: {
isRequired: true,
type: 'oneOf',
value: [
'red',
'yellow',
'green',
],
},
},
{
name: 'arr',
propType: {
isRequired: true,
type: 'arrayOf',
value: 'number',
},
},
{
name: 'halfEmptyObj',
propType: {
isRequired: true,
type: 'shape',
value: [
{
name: 'fun',
propType: {
raw: '(a: string[]) => void',
type: 'func',
value: [
{
name: 'a',
propType: {
type: 'arrayOf',
value: 'string',
},
},
],
},
},
],
},
},
{
name: 'emptyObj',
propType: {
isRequired: true,
type: 'object',
},
},
{
name: 'func',
propType: {
isRequired: true,
raw: '{ (arg: string): number; (a: string): Element; }',
type: 'func',
value: [
{
name: 'arg',
propType: 'string',
},
],
},
},
{
name: 'funcs',
propType: {
isRequired: true,
type: 'shape',
value: [
{
name: 'n',
propType: {
raw: '() => number',
type: 'func',
},
},
{
name: 'a',
propType: {
raw: '(arg: string, num: number) => void',
type: 'func',
value: [
{
name: 'arg',
propType: 'string',
},
{
name: 'num',
propType: 'number',
},
],
},
},
],
},
},
{
name: 'fuzzy',
propType: {
isRequired: true,
type: 'oneOfType',
value: [
'number',
'bool',
{
type: 'shape',
value: [
{
name: 'a',
propType: 'any',
},
{
name: 'sa',
propType: {
type: 'arrayOf',
value: 'string',
},
},
],
},
{
type: 'oneOf',
value: [
'number',
'object',
'test',
],
},
],
},
},
{
name: 'oneOf',
propType: {
isRequired: true,
type: 'oneOfType',
value: [
'bool',
{
type: 'shape',
value: [
{
name: 's',
propType: 'string',
},
{
name: 'n',
propType: 'number',
},
],
},
{
type: 'oneOf',
value: [
'test',
],
},
],
},
},
{
name: 'refFunc',
propType: {
isRequired: true,
raw: '(p: Props) => void',
type: 'func',
value: [
{
name: 'p',
propType: 'object',
},
],
},
},
{
name: 'elementOrOther',
propType: {
isRequired: true,
type: 'oneOfType',
value: [
'element',
{
raw: 'Func',
type: 'func',
value: [
{
name: 'a',
propType: 'string',
},
],
},
],
},
},
{
name: 'obj',
propType: {
isRequired: true,
type: 'shape',
value: [
{
name: 'a',
propType: 'number',
},
{
name: 'arrOfStr',
propType: {
type: 'arrayOf',
value: 'string',
},
},
{
name: 'arrOfObj',
propType: {
type: 'arrayOf',
value: {
type: 'shape',
value: [
{
name: 's',
propType: 'string',
},
{
name: 'n',
propType: 'number',
},
],
},
},
},
{
name: 'func',
propType: 'func',
},
],
},
},
{
name: 'objOf',
propType: {
isRequired: true,
type: 'objectOf',
value: 'number',
},
},
{
name: 'exact',
propType: {
isRequired: true,
type: 'shape',
value: [
{
name: 'a',
propType: 'number',
},
],
},
},
{
name: 'node',
propType: 'node',
},
{
name: 'element',
propType: 'element',
},
{
name: 'elementType',
propType: 'object',
},
{
name: 'union',
propType: {
isRequired: true,
type: 'oneOfType',
value: [
'string',
'number',
{
type: 'shape',
value: [
{
name: 'a',
propType: 'string',
},
],
},
],
},
},
{
name: 'func2',
propType: {
isRequired: true,
raw: 'Func',
type: 'func',
value: [
{
name: 'a',
propType: 'string',
},
],
},
},
{
name: 'html',
propType: {
isRequired: true,
type: 'object',
},
},
{
name: 'loading',
propType: {
type: 'oneOfType',
value: [
'bool',
{
type: 'shape',
value: [
{
name: 'delay',
propType: 'number',
},
],
},
],
},
},
],
screenshot: '',
title: 'ts-component',

View File

@ -46,21 +46,24 @@ export type AnchorContainer = HTMLElement | Window;
interface AnchorProps {
prefixCls?: string;
// className?: string;
// style?: React.CSSProperties;
// children?: React.ReactNode;
// offsetTop?: number;
// bounds?: number;
// affix?: boolean;
// showInkInFixed?: boolean;
// getContainer?: () => AnchorContainer;
// /** Return customize highlight anchor */
// getCurrentAnchor?: () => string;
// onClick?: (e: React.MouseEvent<HTMLElement>, link: { title: React.ReactNode; href: string }) => void;
// /** Scroll to target offset value, if none, it's offsetTop prop value or 0. */
// targetOffset?: number;
// /** Listening event when scrolling change active link */
// onChange?: (currentActiveLink: string) => void;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
offsetTop?: number;
bounds?: number;
affix?: boolean;
showInkInFixed?: boolean;
getContainer?: () => AnchorContainer;
/** Return customize highlight anchor */
getCurrentAnchor?: () => string;
onClick?: (
e: React.MouseEvent<HTMLElement>,
link: { title: React.ReactNode; href: string },
) => void;
/** Scroll to target offset value, if none, it's offsetTop prop value or 0. */
targetOffset?: number;
/** Listening event when scrolling change active link */
onChange?: (currentActiveLink: string) => void;
}
interface AnchorState {
@ -79,7 +82,10 @@ interface AntAnchor {
unregisterLink: (link: string) => void;
activeLink: string | null;
scrollTo: (link: string) => void;
onClick?: (e: React.MouseEvent<HTMLElement>, link: { title: React.ReactNode; href: string }) => void;
onClick?: (
e: React.MouseEvent<HTMLElement>,
link: { title: React.ReactNode; href: string },
) => void;
}
export default class Anchor extends React.Component<AnchorProps, AnchorState> {
@ -249,7 +255,10 @@ export default class Anchor extends React.Component<AnchorProps, AnchorState> {
return;
}
const { offsetTop, bounds, targetOffset } = this.props;
const currentActiveLink = this.getCurrentAnchor(targetOffset !== undefined ? targetOffset : offsetTop || 0, bounds);
const currentActiveLink = this.getCurrentAnchor(
targetOffset !== undefined ? targetOffset : offsetTop || 0,
bounds,
);
this.setCurrentActiveLink(currentActiveLink);
};

View File

@ -1,40 +1,9 @@
import * as React from 'react';
import App from './main-module';
import SubModule from './sub-module';
enum Gender {
MALE,
FEMALE,
}
interface Props {
// str?: string;
// num: number;
// gender: Gender;
// any: any;
// bool: boolean;
// tuple: [1, 'str', true];
// enum: 'red' | 'yellow' | 'green';
// arr: number[];
// obj: {
// a: number;
// [k: string]: number;
// };
// objOf: {
// [k: string]: number;
// };
// exact: {
// a: number;
// };
// empty: {};
node?: React.ReactNode;
// element?: JSX.Element;
// elementType?: React.ElementType;
}
const App = (props: Props) => {
return <div>hello</div>;
App.SubModule = SubModule;
App.defaultProps = {
str: 'str2',
};
App.SubModule = SubModule;
export default App;

View File

@ -0,0 +1,102 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/indent */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/member-ordering */
import * as React from 'react';
import SubModule from './sub-module';
enum Gender {
MALE,
FEMALE,
}
interface Obj {
s: string;
n: number;
}
type Func = (a: string) => JSX.Element;
type Union =
| string
| number
| {
a: string;
};
interface Props {
object: Object;
trigger?: Array<'click' | 'hover' | 'contextMenu'>;
str?: string;
num: number;
gender: Gender;
any: any;
bool: boolean;
tuple: [number, string, true];
enum: 'red' | 'yellow' | 'green';
arr: number[];
halfEmptyObj: {
[k: string]: any;
fun(a: string[]): void;
};
// eslint-disable-next-line @typescript-eslint/ban-types
emptyObj: {};
func(arg: string): number;
funcs: {
n(): number;
a(arg: string, num: number): void;
};
fuzzy:
| boolean
| 'object'
| number
| 'number'
| 'test'
| {
a: any;
sa: string[];
};
oneOf: boolean | 'test' | Obj;
refFunc(p: Props): void;
elementOrOther: JSX.Element | Func;
obj: {
a: number;
arrOfStr: string[];
arrOfObj: Obj[];
func: () => void;
};
objOf: {
[k: string]: number;
};
exact: {
a: number;
};
node?: React.ReactNode;
element?: JSX.Element;
elementType?: React.ElementType;
union: Union;
// eslint-disable-next-line @typescript-eslint/adjacent-overload-signatures
func(a: string): JSX.Element;
func2: Func;
html: HTMLBaseElement;
loading?: boolean | { delay?: number };
}
interface AppProps extends React.FC<Props> {
SubModule?: typeof SubModule;
}
const App: AppProps = () => {
return <div>hello</div>;
};
App.defaultProps = {
object: {
a: '1',
b: '2',
},
func(a) {
return a;
},
str: 'str',
};
export default App;

View File

@ -4,6 +4,19 @@ interface Props {
name: string;
}
export default function SubModule({ name }: Props) {
return <div>hello, {name}</div>;
class SubModule extends React.Component<Props> {
static defaultProps = {
name: 'abc',
};
render() {
const { name } = this.props;
return <div>hello, {name}</div>;
}
}
SubModule.defaultProps = {
name: 'abc',
};
export default SubModule;

View File

@ -0,0 +1,63 @@
{
"name": "@alife/empty",
"version": "1.0.1",
"description": "空组件",
"files": [
"demo/",
"es/",
"lib/",
"build/",
"dist",
"material-meta.json"
],
"main": "src/index.tsx",
"module": "src/index.tsx",
"stylePath": "style.js",
"scripts": {
"start": "build-scripts start",
"build": "build-scripts build",
"test": "build-scripts test",
"prepublishOnly": "npm run build",
"doc": "react-doc-gen",
"lint": "eslint --cache --ext .js,.jsx ./"
},
"keywords": [
"ice",
"react",
"component"
],
"dependencies": {
"classnames": "^2.2.6",
"prop-types": "^15.5.8"
},
"devDependencies": {
"@alife/build-plugin-lowcode": "^1.0.7",
"@alib/build-scripts": "^0.1.3",
"@alifd/adaptor-generate": "^0.1.3",
"build-plugin-component": "^0.2.0",
"build-plugin-fusion": "^0.1.0",
"build-plugin-fusion-cool": "^0.1.0",
"build-plugin-moment-locales": "^0.1.0",
"react": "^16.3.0",
"react-dom": "^16.3.0",
"@ice/spec": "^1.0.0",
"eslint": "^6.0.1",
"@alifd/next": "1.x",
"@types/react": "^16.9.13",
"@types/react-dom": "^16.9.4"
},
"peerDependencies": {
"react": "^16.3.0",
"@alifd/next": "1.x"
},
"componentConfig": {
"name": "Empty",
"title": "empty",
"category": "DataDisplay"
},
"publishConfig": {
"registry": "https://registry.npm.alibaba-inc.com"
},
"license": "MIT",
"homepage": "https://unpkg.alibaba-inc.com/@alife/empty@1.0.1/build/index.html"
}

View File

@ -0,0 +1,22 @@
import * as React from 'react';
import classNames from 'classnames';
import './index';
interface DefaultEmptyImg {
className?: string;
imageStyle?: React.CSSProperties;
}
export const DefaultEmptyImg = (props: DefaultEmptyImg) => {
const { className, imageStyle, ...restProps } = props;
const prefixCls = 'design-empty-default';
const alt = 'empty';
return (
<div className={classNames(prefixCls, className)} {...restProps}>
<div className={`${prefixCls}-image`} style={imageStyle}>
<img alt={alt} src="https://img.alicdn.com/tfs/TB13G0LTNv1gK0jSZFFXXb0sXXa-54-54.svg" />
</div>
<p className={`${prefixCls}-description`}></p>
</div>
);
};

View File

@ -0,0 +1,49 @@
.design-empty {
margin: 0 8px;
font-size: 14px;
line-height: 1.5715;
text-align: center;
&-image {
height: 200px;
margin-bottom: 4px;
img {
height: 100%;
}
svg {
height: 100%;
margin: auto;
}
}
&-description {
margin: 0;
}
&-footer {
margin-top: 16px;
}
}
.design-empty-default {
margin: 0 8px;
font-size: 14px;
line-height: 1.5715;
text-align: center;
&-image {
height: 54px;
margin-bottom: 8px;
img {
height: 100%;
}
}
&-description {
font-size: 14px;
color: #c2c2c2;
}
}

View File

@ -0,0 +1,71 @@
import * as React from 'react';
import classNames from 'classnames';
import { DefaultEmptyImg } from './empty';
export interface EmptyProps {
className?: string;
style?: React.CSSProperties;
imageStyle?: React.CSSProperties;
image?: React.ReactNode | string;
description?: React.ReactNode;
children?: React.ReactNode;
type?: 'default' | 'custom'; // default 默认, custom 表示自定义
}
const prefixCls = 'design-empty';
interface EmptyType extends React.FC<EmptyProps> {
IMAGE_TYPE_SERVERBUSY: string; //服务器繁忙
IMAGE_TYPE_SERVERNOFOUND: string; // 404
IMAGE_TYPE_FILENOFOUND: string; //文件不存在
IMAGE_TYPE_PROJECTNOFOUND: string; // 项目不存在
IMAGE_TYPE_EMPTY: string; //空
}
const Empty: EmptyType = (props: EmptyProps) => {
const {
className,
image = 'Empty.IMAGE_TYPE_EMPTY',
description,
children,
imageStyle,
type = 'custom',
...restProps
} = props;
if (type === 'default') {
return <DefaultEmptyImg />;
}
let imageNode: React.ReactNode = null;
const alt = typeof description === 'string' ? description : 'empty';
if (typeof image === 'string') {
imageNode = <img alt={alt} src={image} />;
} else {
imageNode = image;
}
return (
<div className={classNames(prefixCls, className)} {...restProps}>
<div className={`${prefixCls}-image`} style={imageStyle}>
{imageNode}
</div>
{description && <p className={`${prefixCls}-description`}>{description}</p>}
{children && <div className={`${prefixCls}-footer`}>{children}</div>}
</div>
);
};
//服务器繁忙
Empty.IMAGE_TYPE_SERVERBUSY = 'https://img.alicdn.com/tfs/TB1qPJvTFY7gK0jSZKzXXaikpXa-400-400.png';
//404
Empty.IMAGE_TYPE_SERVERNOFOUND =
'https://img.alicdn.com/tfs/TB18gVGTUH1gK0jSZSyXXXtlpXa-400-400.png';
//文件不存在
Empty.IMAGE_TYPE_FILENOFOUND = 'https://img.alicdn.com/tfs/TB1.ClQTUT1gK0jSZFrXXcNCXXa-400-400.png';
//项目不存在
Empty.IMAGE_TYPE_PROJECTNOFOUND =
'https://img.alicdn.com/tfs/TB1ZWumfcieb18jSZFvXXaI3FXa-400-400.png';
//空
Empty.IMAGE_TYPE_EMPTY = 'https://img.alicdn.com/tfs/TB13G0LTNv1gK0jSZFFXXb0sXXa-54-54.svg';
export default Empty;

View File

@ -6,6 +6,7 @@ import { getFromFixtures } from './helpers';
const multiExportedComptPath = getFromFixtures('multiple-exported-component');
const singleExportedComptPath = getFromFixtures('single-exported-component');
const tsComponent = getFromFixtures('ts-component');
const tsComponent2 = getFromFixtures('ts-component2');
test('materialize single exported component by local', async t => {
const options: IMaterializeOptions = {
@ -39,3 +40,14 @@ test('ts component by local', async t => {
t.snapshot(actual);
});
test('ts component 2 by local', async t => {
const options: IMaterializeOptions = {
entry: tsComponent2,
accesser: 'local',
};
const actual = await parse(options);
t.snapshot(actual);
});