refactor: material parser code style

1. 修复eslint问题
2. instanceOf => any
3. 修复node类型解析失败问题

Link: https://code.aone.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/codereview/3716330

* refactor: material parser code style
This commit is contained in:
gengyang.gy 2020-09-12 17:39:13 +08:00 committed by wuji.xwt
parent 0a29c80928
commit 430d522353
47 changed files with 1220 additions and 14338 deletions

2
.vscode/launch.json vendored
View File

@ -9,7 +9,7 @@
"type": "node",
"request": "launch",
"runtimeExecutable": "${workspaceFolder}/packages/material-parser/node_modules/.bin/ava",
"runtimeArgs": ["debug", "--break", "${file}"]
"runtimeArgs": ["debug", "--break", "${workspaceFolder}/packages/material-parser/test/antd.ts"]
}
]
}

View File

@ -12,6 +12,8 @@
"scripts": {
"build": "./scripts/build.sh",
"clean": "rm -rf ./packages/*/lib ./packages/*/es ./packages/*/dist ./packages/*/build",
"lint": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet",
"lint:fix": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet --fix",
"pub": "lerna publish --force-publish --cd-version prepatch",
"setup": "./scripts/setup.sh",
"start": "./scripts/start.sh",
@ -19,9 +21,7 @@
"test": "lerna run test --stream",
"test:snapshot": "lerna run test:snapshot",
"xima:fix": "xima fix",
"xima:scan": "xima scan",
"lint": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet",
"lint:fix": "eslint --ext .ts,.tsx,.js,.jsx ./ --quiet --fix"
"xima:scan": "xima scan"
},
"husky": {
"hooks": {

View File

@ -0,0 +1,2 @@
test/fixtures/**
lib/**

View File

@ -0,0 +1,3 @@
module.exports = {
extends: 'eslint-config-ali/typescript/react',
};

View File

@ -12,3 +12,5 @@
cd demo
node index.js
```
## API

View File

@ -1,3 +1,4 @@
/* eslint-disable react/forbid-prop-types,react/no-unused-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
@ -37,7 +38,11 @@ Demo.propTypes = {
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// An object that could be one of many types
optionalUnion: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Demo)]),
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Demo),
]),
// An array of a certain type
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

View File

@ -63,6 +63,7 @@
"prop-types": "^15.7.2",
"react-docgen": "^5.3.0",
"react-docgen-typescript": "^1.16.5",
"safe-eval": "^0.4.1",
"semver": "^7.1.3",
"short-uuid": "^3.1.1",
"typescript": "^3.9.5",

View File

@ -5,6 +5,6 @@ export * from './schema/types';
/**
* Dev helper
*/
export const debug = _debug('lowcode');
export const debug = _debug('lowcode:mat');
export const enableDebug = () => _debug.enable('lowcode:*');
export const disableDebug = () => _debug.disable();

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;
}

View File

@ -1,7 +1,7 @@
import { debug, ComponentMeta } from './core';
import { IMaterialParsedModel, IMaterialScanModel } from './types';
const log = debug.extend('mat');
const log = debug.extend('gen');
export default async function (
matScanModel: IMaterialScanModel,
@ -41,6 +41,7 @@ export async function genManifest(
title: matScanModel.pkgName,
docUrl: '',
screenshot: '',
devMode: 'proCode', // 需要入料的组件都是源码模式,低代码组件在平台上即可直接生成描述
npm: {
package: matScanModel.pkgName,
version: matScanModel.pkgVersion,

View File

@ -1,12 +1,11 @@
import spawn from 'cross-spawn-promise';
import { ensureDir, ensureFile, writeFile } from 'fs-extra';
import { join } from 'path';
import semver from 'semver';
import uuid from 'short-uuid';
import { debug } from './core';
import { IMaterializeOptions } from './types';
const log = debug.extend('mat');
const log = debug.extend('localize');
/**
*

View File

@ -3,6 +3,9 @@ import parseJS from './js';
import parseTS from './ts';
import { install, installPeerDeps, installTypeModules } from '../utils';
import { IMaterialScanModel } from '../types';
import { debug } from '../core';
const log = debug.extend('parse');
export interface IParseArgs extends IMaterialScanModel {
accesser?: 'online' | 'local';
@ -15,7 +18,11 @@ export interface IParseArgs extends IMaterialScanModel {
}
export default async (args: IParseArgs) => {
const { typingsFileAbsolutePath, mainFileAbsolutePath, moduleFileAbsolutePath = mainFileAbsolutePath } = args;
const {
typingsFileAbsolutePath,
mainFileAbsolutePath,
moduleFileAbsolutePath = mainFileAbsolutePath,
} = args;
if (args.accesser === 'local') {
if (moduleFileAbsolutePath.endsWith('ts') || moduleFileAbsolutePath.endsWith('tsx')) {
await install(args);
@ -48,7 +55,7 @@ export default async (args: IParseArgs) => {
}
return info;
} catch (e) {
console.error(e);
log(e);
// if error, use static js parsing instead
return parseJS(moduleFileAbsolutePath);
}

View File

@ -1,5 +1,8 @@
// import { debug } from '../../../core';
const { namedTypes: t, NodePath } = require('ast-types');
// const log = debug.extend('parse:js');
type NodePathType = typeof NodePath;
const {
getPropertyName,
@ -7,7 +10,6 @@ const {
getMemberValuePath,
isReactForwardRefCall,
printValue,
resolveExportDeclaration,
resolveToValue,
} = require('react-docgen').utils;
const resolveFunctionDefinitionToReturnValue = require('react-docgen/dist/utils/resolveFunctionDefinitionToReturnValue');
@ -29,16 +31,17 @@ function getDefaultValue(path: NodePathType) {
node = path.node;
try {
defaultValue = printValue(path);
} catch (e) {}
} catch (e) {
// log(e);
// TODO
}
}
}
if (typeof defaultValue !== 'undefined') {
return {
value: defaultValue,
computed:
t.CallExpression.check(node) ||
t.MemberExpression.check(node) ||
t.Identifier.check(node),
t.CallExpression.check(node) || t.MemberExpression.check(node) || t.Identifier.check(node),
};
}
@ -55,10 +58,7 @@ function getStatelessPropsPath(componentDefinition: any) {
}
function getDefaultPropsPath(componentDefinition: any) {
let defaultPropsPath = getMemberValuePath(
componentDefinition,
'defaultProps',
);
let defaultPropsPath = getMemberValuePath(componentDefinition, 'defaultProps');
if (!defaultPropsPath) {
return null;
}
@ -71,9 +71,7 @@ function getDefaultPropsPath(componentDefinition: any) {
if (t.FunctionExpression.check(defaultPropsPath.node)) {
// Find the value that is returned from the function and process it if it is
// an object literal.
const returnValue = resolveFunctionDefinitionToReturnValue(
defaultPropsPath,
);
const returnValue = resolveFunctionDefinitionToReturnValue(defaultPropsPath);
if (returnValue && t.ObjectExpression.check(returnValue.node)) {
defaultPropsPath = returnValue;
}
@ -81,16 +79,11 @@ function getDefaultPropsPath(componentDefinition: any) {
return defaultPropsPath;
}
function getDefaultValuesFromProps(
properties: any[],
documentation: any,
isStateless: boolean,
) {
function getDefaultValuesFromProps(properties: any[], documentation: any, isStateless: boolean) {
properties
// Don't evaluate property if component is functional and the node is not an AssignmentPattern
.filter(
propertyPath => !isStateless ||
t.AssignmentPattern.check(propertyPath.get('value').node),
propertyPath => !isStateless || t.AssignmentPattern.check(propertyPath.get('value').node),
)
.forEach(propertyPath => {
if (t.Property.check(propertyPath.node)) {
@ -99,9 +92,7 @@ function getDefaultValuesFromProps(
const propDescriptor = documentation.getPropDescriptor(propName);
const defaultValue = getDefaultValue(
isStateless
? propertyPath.get('value', 'right')
: propertyPath.get('value'),
isStateless ? propertyPath.get('value', 'right') : propertyPath.get('value'),
);
if (defaultValue) {
propDescriptor.defaultValue = defaultValue;
@ -119,10 +110,7 @@ function getDefaultValuesFromProps(
});
}
export default function defaultPropsHandler(
documentation: any,
componentDefinition: any,
) {
export default function defaultPropsHandler(documentation: any, componentDefinition: any) {
let statelessProps = null;
const defaultPropsPath = getDefaultPropsPath(componentDefinition);
/**
@ -134,17 +122,9 @@ export default function defaultPropsHandler(
// Do both statelessProps and defaultProps if both are available so defaultProps can override
if (statelessProps && t.ObjectPattern.check(statelessProps.node)) {
getDefaultValuesFromProps(
statelessProps.get('properties'),
documentation,
true,
);
getDefaultValuesFromProps(statelessProps.get('properties'), documentation, true);
}
if (defaultPropsPath && t.ObjectExpression.check(defaultPropsPath.node)) {
getDefaultValuesFromProps(
defaultPropsPath.get('properties'),
documentation,
false,
);
getDefaultValuesFromProps(defaultPropsPath.get('properties'), documentation, false);
}
}

View File

@ -7,9 +7,9 @@
import { namedTypes as t } from 'ast-types';
import getTSType from '../utils/getTSType';
import getRoot from '../utils/getRoot';
import parseTS from '../../ts';
import getFlowTypeFromReactComponent, { applyToFlowTypeProperties } from '../utils/getFlowTypeFromReactComponent';
import getFlowTypeFromReactComponent, {
applyToFlowTypeProperties,
} from '../utils/getFlowTypeFromReactComponent';
const { unwrapUtilityType } = require('react-docgen/dist/utils/flowUtilityTypes');
const { getFlowType, getPropertyName, resolveToValue } = require('react-docgen').utils;

View File

@ -26,9 +26,9 @@ export default function parse(filePath: string): IMaterialParsedModel[] {
const item: any = transformItem(name, info.props[name]);
acc.push(item);
} catch (e) {
} finally {
return acc;
// TODO
}
return acc;
}, []);
res.push({
componentName: info.displayName,

View File

@ -3,7 +3,7 @@ import { uniqBy } from 'lodash';
import checkIsIIFE from './checkIsIIFE';
import resolveHOC from './resolveHOC';
import resolveIIFE from './resolveIIFE';
import resolveImport, { isImportLike } from './resolveImport';
import resolveImport from './resolveImport';
import resolveTranspiledClass from './resolveTranspiledClass';
import isStaticMethod from './isStaticMethod';
import findAssignedMethods from './findAssignedMethods';
@ -253,14 +253,16 @@ function getSubComponents(path: any, scope: any, cache: ICache) {
value: def.flatMap((x: any) => x).filter((x: any) => isComponentDefinition(x)),
};
})
.map(({ subName, localName, value }: IMethodsPath) => value.map((x: any) => ({
subName,
localName,
value: x,
})))
.map(({ subName, localName, value }: IMethodsPath) => {
return value.map((x: any) => ({
subName,
localName,
value: x,
}));
})
// @ts-ignore
.flatMap((x: any) => x)
.map(({ subName, localName, value }: IMethodsPath) => {
.map(({ subName, value }: IMethodsPath) => {
const __meta = {
subName,
exportName: path.__meta && path.__meta.exportName,

View File

@ -1,10 +1,6 @@
import { namedTypes as t, visit } from 'ast-types';
import { namedTypes as t } from 'ast-types';
const {
isReactCreateClassCall,
isReactForwardRefCall,
resolveToValue,
} = require('react-docgen').utils;
const { isReactCreateClassCall, isReactForwardRefCall } = require('react-docgen').utils;
/**
* If the path is a call expression, it recursively resolves to the

View File

@ -1,4 +1,4 @@
import { builders, namedTypes as t, NodePath, visit } from 'ast-types';
import { builders, NodePath, visit } from 'ast-types';
/**
* If the path is a call expression, it recursively resolves to the
* rightmost argument, stopping if it finds a React.createClass call expression
@ -17,11 +17,7 @@ export default function resolveTranspiledClass(path: any) {
builders.blockStatement([
builders.returnStatement(
builders.jsxElement(
builders.jsxOpeningElement(
builders.jsxIdentifier('div'),
[],
true,
),
builders.jsxOpeningElement(builders.jsxIdentifier('div'), [], true),
),
),
]),

View File

@ -14,5 +14,5 @@ export function get(scope: string, name: string) {
}
export function has(scope: string, name: string) {
return cache[scope] && cache[scope].hasOwnProperty(name);
return cache[scope] && Object.prototype.hasOwnProperty.call(cache[scope], name);
}

View File

@ -9,10 +9,15 @@ function makeProxy(target: { [name: string]: any }, meta: any = {}): any {
if (prop === '__isProxy') return true;
if (prop === '__getRaw') return () => target;
if (prop === '__getMeta') return () => meta;
return meta.hasOwnProperty(prop) ? meta[prop] : obj[prop];
return Object.prototype.hasOwnProperty.call(meta, prop) ? meta[prop] : obj[prop];
// return obj[prop];
},
has: (obj, prop) => obj.hasOwnProperty(prop) || meta.hasOwnProperty(prop),
has: (obj, prop) => {
return (
Object.prototype.hasOwnProperty.call(obj, prop) ||
Object.prototype.hasOwnProperty.call(meta, prop)
);
},
});
}

View File

@ -4,7 +4,6 @@ import { isEmpty } from 'lodash';
import parsePropTypes from 'parse-prop-types';
import PropTypes from 'prop-types';
import { transformItem } from '../transform';
import { IParseArgs } from '../index';
import requireInSandbox from './requireInSandbox';
export interface IComponentInfo {
@ -29,7 +28,7 @@ const reservedKeys = [
];
function getKeys(com: any) {
const keys = Object.keys(com).filter((x) => {
const keys = Object.keys(com).filter(x => {
return !reservedKeys.includes(x) && !x.startsWith('_');
});
@ -37,7 +36,11 @@ function getKeys(com: any) {
}
function isComponent(obj: any) {
return typeof obj === 'function' && (obj.hasOwnProperty('propTypes') || obj.hasOwnProperty('defaultProps'));
return (
typeof obj === 'function' &&
(Object.prototype.hasOwnProperty.call(obj, 'propTypes') ||
Object.prototype.hasOwnProperty.call(obj, 'defaultProps'))
);
}
export default function (filePath: string) {
@ -50,7 +53,7 @@ export default function (filePath: string) {
if (Com.__esModule) {
const keys = getKeys(Com);
keys.forEach((k) => {
keys.forEach(k => {
if (isComponent(Com[k])) {
components.push({
component: Com[k],
@ -75,8 +78,8 @@ export default function (filePath: string) {
const keys = getKeys(item.component);
const subs = keys
.filter((k) => isComponent(item.component[k]))
.map((k) => ({
.filter(k => isComponent(item.component[k]))
.map(k => ({
component: item.component[k],
meta: {
...item.meta,
@ -91,14 +94,14 @@ export default function (filePath: string) {
const result = components.reduce((acc: any, { meta, component }) => {
const componentInfo = parsePropTypes(component);
if (!isEmpty(componentInfo)) {
const props = Object.keys(componentInfo).reduce((acc: any[], name) => {
const props = Object.keys(componentInfo).reduce((acc2: any[], name) => {
try {
const item: any = transformItem(name, componentInfo[name]);
acc.push(item);
acc2.push(item);
} catch (e) {
} finally {
return acc;
// TODO
}
return acc2;
}, []);
return [

View File

@ -1,4 +1,8 @@
import { omit, pick } from 'lodash';
import { omit, pick, isNil } from 'lodash';
import { safeEval, isEvaluable } from '../utils';
import { debug } from '../core';
const log = debug.extend('parse:transform');
export function transformType(itemType: any) {
if (typeof itemType === 'string') return itemType;
@ -7,7 +11,7 @@ export function transformType(itemType: any) {
// return name;
// }
if (computed !== undefined && value) {
return eval(value);
return safeEval(value);
}
const result: any = {
type: name,
@ -24,16 +28,31 @@ export function transformType(itemType: any) {
case 'symbol':
case 'object':
case 'null':
case 'array':
case 'element':
case 'node':
break;
case 'literal':
return eval(value);
return safeEval(value);
case 'enum':
case 'tuple':
case 'oneOf':
result.type = 'oneOf';
result.value = value.map(transformType);
break;
case 'union':
case 'union': {
const { raw } = itemType;
if (raw) {
if (raw.match(/ReactNode$/)) {
result.type = 'node';
break;
} else if (raw.match(/Element$/)) {
result.type = 'element';
break;
}
}
}
// eslint-disable-next-line no-fallthrough
case 'oneOfType':
result.type = 'oneOfType';
result.value = value.map(transformType);
@ -77,12 +96,9 @@ export function transformType(itemType: any) {
result.value = properties
.filter((item: any) => typeof item.key !== 'object')
.map((prop: any) => {
const {
key,
value: { name, ...others },
} = prop;
const { key } = prop;
return transformItem(key, {
...others,
...omit(prop.value, 'name'),
type: pick(prop.value, ['name', 'value']),
});
});
@ -90,7 +106,6 @@ export function transformType(itemType: any) {
break;
}
case 'objectOf':
case 'arrayOf':
case 'instanceOf':
result.value = transformType(value);
break;
@ -107,18 +122,19 @@ export function transformType(itemType: any) {
});
});
break;
case (name.match('ReactNode$') || {}).input:
case (name.match(/ReactNode$/) || {}).input:
result.type = 'node';
break;
case (name.match('Element$') || {}).input:
case (name.match(/Element$/) || {}).input:
result.type = 'element';
break;
case (name.match('ElementType$') || {}).input:
result.type = 'elementType';
break;
// case (name.match(/ElementType$/) || {}).input:
// result.type = 'elementType';
// break;
default:
result.type = 'instanceOf';
result.value = name;
// result.type = 'instanceOf';
// result.value = name;
result.type = 'any';
break;
}
if (Object.keys(result).length === 1) {
@ -128,7 +144,15 @@ export function transformType(itemType: any) {
}
export function transformItem(name: string, item: any) {
const { description, flowType, tsType, type = tsType || flowType, required, defaultValue, ...others } = item;
const {
description,
flowType,
tsType,
type = tsType || flowType,
required,
defaultValue,
...others
} = item;
const result: any = {
name,
};
@ -147,11 +171,19 @@ export function transformItem(name: string, item: any) {
result.description = description;
}
}
if (defaultValue !== undefined) {
try {
const value = eval(defaultValue.value);
result.defaultValue = value;
} catch (e) {}
if (!isNil(defaultValue) && typeof defaultValue === 'object' && isEvaluable(defaultValue)) {
if (defaultValue === null) {
result.defaultValue = defaultValue;
} else {
try {
const value = safeEval(defaultValue.value);
if (isEvaluable(value)) {
result.defaultValue = value;
}
} catch (e) {
log(e);
}
}
}
if (result.propType === undefined) {
delete result.propType;

View File

@ -1,8 +1,17 @@
import { Parser, ComponentDoc } from 'react-docgen-typescript';
import ts, { SymbolFlags, TypeFlags } from 'typescript';
import { isEmpty, isEqual } from 'lodash';
import { debug } from '../../core';
import { Json } from '../../types';
import { transformItem } from '../transform';
const log = debug.extend('parse:ts');
type ExtendedType = ts.Type & {
id: string;
typeArguments: any[];
};
function getSymbolName(symbol: ts.Symbol) {
// @ts-ignore
const prefix: string = symbol.parent && getSymbolName(symbol.parent);
@ -32,13 +41,10 @@ function getDocgenTypeHelper(
parentIds: number[] = [],
isRequired = false,
): any {
function isTuple(type: ts.Type) {
function isTuple(_type: ts.Type) {
// @ts-ignore use internal methods
return checker.isArrayLikeType(type) && !checker.isArrayType(type);
return checker.isArrayLikeType(_type) && !checker.isArrayType(_type);
}
// if (type.aliasSymbol && type.aliasSymbol.getName() === 'ReactNode') {
// return 'children';
// }
let required: boolean;
if (isRequired !== undefined) {
required = isRequired;
@ -46,7 +52,7 @@ function getDocgenTypeHelper(
required = !(type.flags & SymbolFlags.Optional) || isRequired;
}
function makeResult(typeInfo: object) {
function makeResult(typeInfo: Json) {
if (skipRequired) {
return {
raw: checker.typeToString(type),
@ -61,7 +67,7 @@ function getDocgenTypeHelper(
}
}
function getShapeFromArray(symbolArr: ts.Symbol[], type: ts.Type) {
function getShapeFromArray(symbolArr: ts.Symbol[], _type: ts.Type) {
const shape: Array<{
key:
| {
@ -69,7 +75,7 @@ function getDocgenTypeHelper(
}
| string;
value: any;
}> = symbolArr.map((prop) => {
}> = symbolArr.map(prop => {
const propType = checker.getTypeOfSymbolAtLocation(
prop,
// @ts-ignore
@ -83,19 +89,19 @@ function getDocgenTypeHelper(
propType,
false,
// @ts-ignore
[...parentIds, type.id],
[...parentIds, _type.id],
// @ts-ignore
prop?.valueDeclaration?.questionToken ? false : undefined,
),
};
});
// @ts-ignore use internal methods
if (checker.isArrayLikeType(type)) {
if (checker.isArrayLikeType(_type)) {
return shape;
}
if (type.getStringIndexType()) {
if (_type.getStringIndexType()) {
// @ts-ignore use internal methods
if (!type.stringIndexInfo) {
if (!_type.stringIndexInfo) {
return shape;
}
shape.push({
@ -103,11 +109,14 @@ function getDocgenTypeHelper(
name: 'string',
},
// @ts-ignore use internal methods
value: getDocgenTypeHelper(checker, type.stringIndexInfo.type, false, [...parentIds, type.id]),
value: getDocgenTypeHelper(checker, _type.stringIndexInfo.type, false, [
...parentIds,
(_type as ExtendedType).id,
]),
});
} else if (type.getNumberIndexType()) {
} else if (_type.getNumberIndexType()) {
// @ts-ignore use internal methods
if (!type.numberIndexInfo) {
if (!_type.numberIndexInfo) {
return shape;
}
shape.push({
@ -116,32 +125,34 @@ function getDocgenTypeHelper(
},
// @ts-ignore use internal methods
value: getDocgenTypeHelper(checker, type.numberIndexInfo.type, false, [...parentIds, type.id]),
value: getDocgenTypeHelper(checker, _type.numberIndexInfo.type, false, [
...parentIds,
(_type as ExtendedType).id,
]),
});
}
return shape;
}
function getShape(type: ts.Type) {
const { symbol } = type;
function getShape(_type: ts.Type) {
const { symbol } = _type;
if (symbol && symbol.members) {
// @ts-ignore
const props: ts.Symbol[] = Array.from(symbol.members.values());
return getShapeFromArray(
props.filter((prop) => prop.getName() !== '__index'),
type,
props.filter(prop => prop.getName() !== '__index'),
_type,
);
} else {
// @ts-ignore
const args = type.resolvedTypeArguments || [];
const props = checker.getPropertiesOfType(type);
const shape = getShapeFromArray(props.slice(0, args.length), type);
const args = _type.resolvedTypeArguments || [];
const props = checker.getPropertiesOfType(_type);
const shape = getShapeFromArray(props.slice(0, args.length), _type);
return shape;
}
}
const pattern = /^__global\.(.+)$/;
// @ts-ignore
if (parentIds.includes(type.id)) {
return makeResult({
@ -191,21 +202,26 @@ function getDocgenTypeHelper(
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, [...parentIds, type.id])),
});
} 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),
value: props.map(p => p.value),
});
// @ts-ignore
} else if (checker.isArrayType(type)) {
return makeResult({
name: 'Array',
// @ts-ignore
elements: [getDocgenTypeHelper(checker, type.typeArguments[0], false, [...parentIds, type.id])],
elements: [
getDocgenTypeHelper(checker, (type as ExtendedType).typeArguments[0], false, [
...parentIds,
(type as any).id,
]),
],
});
} else if (type.aliasSymbol) {
return makeResult({
@ -254,7 +270,10 @@ interface SymbolWithMeta extends ts.Symbol {
};
}
export default function parseTS(filePathOrPaths: string | string[], parserOpts: any = {}): ComponentDoc[] {
export default function parseTS(
filePathOrPaths: string | string[],
parserOpts: any = {},
): ComponentDoc[] {
const filePaths = Array.isArray(filePathOrPaths) ? filePathOrPaths : [filePathOrPaths];
const program = ts.createProgram(filePaths, compilerOptions);
@ -264,8 +283,8 @@ export default function parseTS(filePathOrPaths: string | string[], parserOpts:
const checker = program.getTypeChecker();
const result = filePaths
.map((filePath) => program.getSourceFile(filePath))
.filter((sourceFile) => typeof sourceFile !== 'undefined')
.map(filePath => program.getSourceFile(filePath))
.filter(sourceFile => typeof sourceFile !== 'undefined')
.reduce((docs: any[], sourceFile) => {
const moduleSymbol = checker.getSymbolAtLocation(sourceFile as ts.Node);
@ -291,7 +310,7 @@ export default function parseTS(filePathOrPaths: string | string[], parserOpts:
subName: exportName ? name : '',
exportName: exportName || name,
};
if (docs.find((x) => isEqual(x.meta, meta))) {
if (docs.find(x => isEqual(x.meta, meta))) {
continue;
}
docs.push({
@ -303,7 +322,10 @@ export default function parseTS(filePathOrPaths: string | string[], parserOpts:
continue;
}
const type = checker.getTypeOfSymbolAtLocation(sym, sym.valueDeclaration || sym.declarations[0]);
const type = checker.getTypeOfSymbolAtLocation(
sym,
sym.valueDeclaration || sym.declarations[0],
);
Array.prototype.push.apply(
exportSymbols,
@ -323,10 +345,9 @@ export default function parseTS(filePathOrPaths: string | string[], parserOpts:
const item: any = transformItem(name, info.props[name]);
acc.push(item);
} catch (e) {
console.log('error', e);
} finally {
return acc;
log(e);
}
return acc;
}, []);
res.push({
componentName: info?.meta?.exportName || info.displayName,

View File

@ -0,0 +1,4 @@
export interface Json {
[x: string]: string | number | boolean | Date | Json | JsonArray;
}
export type JsonArray = Array<string | number | boolean | Date | Json | JsonArray>;

View File

@ -1,11 +1,9 @@
/**
*
*/
enum ChannelType {
export enum ChannelType {
/** 本地 */
LOCAL = 'local',
/** 在线 */
ONLINE = 'online',
}
export default ChannelType;

View File

@ -1,7 +1,7 @@
/**
*
*/
enum EcologyType {
export enum EcologyType {
/** react 生态 */
REACT = 'react',
/** vue 生态 */
@ -11,5 +11,3 @@ enum EcologyType {
/** angular 生态 */
ANGULAR = 'angular',
}
export default EcologyType;

View File

@ -1,9 +0,0 @@
/**
*
*/
enum ExtensionName {
/** 配置 manifest */
CONFIGMANIFEST = 'mat:config:manifest',
}
export default ExtensionName;

View File

@ -4,7 +4,7 @@ import { ComponentMeta } from '../core';
*
* @interface IAccesser
*/
interface IAccesser {
export interface IAccesser {
/**
*
* @returns {Promise<IMaterialinSchema>}
@ -12,5 +12,3 @@ interface IAccesser {
*/
access(): Promise<ComponentMeta[]>;
}
export default IAccesser;

View File

@ -1,15 +0,0 @@
/**
* - bundle.js
* @interface ICompiler
*/
interface ICompiler {
/**
*
* @param {{ [key: string]: any }} config webpack
* @returns {Promise<void>}
* @memberof ICompiler
*/
compile(config: { [key: string]: any }): Promise<void>;
}
export default ICompiler;

View File

@ -3,7 +3,7 @@ import { ComponentMeta } from '../core';
* manifest
*
*/
type IExtensionConfigManifest = (params: {
export type IExtensionConfigManifest = (params: {
manifestObj: ComponentMeta; // manifest 配置对象
manifestFilePath: string; // manifest 文件默认路径
}) => Promise<{
@ -11,5 +11,3 @@ type IExtensionConfigManifest = (params: {
manifestFilePath: string; // manifest 文件路径
manifestObj: ComponentMeta; // manifest 文件对象
}>;
export default IExtensionConfigManifest;

View File

@ -1,7 +1,7 @@
/**
*
*/
interface IMaterialScanModel {
export interface IMaterialScanModel {
/** 当前包名 */
pkgName: string;
/** 当前包版本 */
@ -19,5 +19,3 @@ interface IMaterialScanModel {
/** typings文件绝对路径 */
typingsFileAbsolutePath?: string;
}
export default IMaterialScanModel;

View File

@ -1,11 +1,8 @@
import ExtensionName from './ExtensionName';
import IExtensionConfigManifest from './IExtensionConfigManifest';
/**
*
* @interface IMaterializeOptions
*/
interface IMaterializeOptions {
export interface IMaterializeOptions {
/**
* ()
*
@ -35,5 +32,3 @@ interface IMaterializeOptions {
*/
npmClient?: string;
}
export default IMaterializeOptions;

View File

@ -1,4 +1,4 @@
import { NodePath, Path } from 'ast-types';
import { Path } from 'ast-types';
export interface IFileMeta {
src: string;

View File

@ -1,21 +1,8 @@
import ChannelType from './ChannelType';
import EcologyType from './EcologyType';
import ExtensionName from './ExtensionName';
import IAccesser from './IAccesser';
import ICompiler from './ICompiler';
import IExtensionConfigManifest from './IExtensionConfigManifest';
import IMaterializeOptions from './IMaterializeOptions';
import IMaterialScanModel from './IMaterialScanModel';
import { IMaterialParsedModel } from './IMaterialParsedModel';
export {
ExtensionName,
IExtensionConfigManifest,
IMaterialParsedModel,
IMaterializeOptions,
IMaterialScanModel,
ChannelType,
EcologyType,
IAccesser,
ICompiler,
};
export * from './ChannelType';
export * from './EcologyType';
export * from './IAccesser';
export * from './IExtensionConfigManifest';
export * from './IMaterializeOptions';
export * from './IMaterialScanModel';
export * from './IMaterialParsedModel';
export * from './Basic';

View File

@ -1,27 +1,42 @@
import { pathExists, readFileSync, writeFile } from 'fs-extra';
import { isPlainObject } from 'lodash';
import safeEval from 'safe-eval';
import * as path from 'path';
import spawn from 'cross-spawn-promise';
export async function isNPMInstalled(args: { workDir: string; moduleDir: string; npmClient?: string }) {
return await pathExists(path.join(args.workDir, 'node_modules'));
export async function isNPMInstalled(args: {
workDir: string;
moduleDir: string;
npmClient?: string;
}) {
return pathExists(path.join(args.workDir, 'node_modules'));
}
export async function install(args: { workDir: string; moduleDir: string; npmClient?: string }) {
if (await isNPMInstalled(args)) return;
const { workDir, moduleDir, npmClient = 'tnpm' } = args;
const { workDir, npmClient = 'tnpm' } = args;
try {
await spawn(npmClient, ['i'], { stdio: 'inherit', cwd: workDir } as any);
} catch (e) {}
} catch (e) {
// TODO
}
}
export async function installTypeScript(args: { workDir: string; moduleDir: string; npmClient?: string }) {
export async function installTypeScript(args: {
workDir: string;
moduleDir: string;
npmClient?: string;
}) {
if (await isNPMInstalled(args)) return;
const { workDir, moduleDir, npmClient = 'tnpm' } = args;
const { workDir, npmClient = 'tnpm' } = args;
await spawn(npmClient, ['i', 'typescript'], { stdio: 'inherit', cwd: workDir } as any);
}
export async function installPeerDeps(args: { workDir: string; moduleDir: string; npmClient?: string }) {
export async function installPeerDeps(args: {
workDir: string;
moduleDir: string;
npmClient?: string;
}) {
const { workDir, moduleDir, npmClient = 'tnpm' } = args;
const modulePkgJsonPath = path.resolve(moduleDir, 'package.json');
if (!(await pathExists(modulePkgJsonPath))) {
@ -43,7 +58,11 @@ export async function installPeerDeps(args: { workDir: string; moduleDir: string
await spawn(npmClient, ['i'], { stdio: 'inherit', cwd: workDir } as any);
}
export async function installTypeModules(args: { workDir: string; moduleDir: string; npmClient?: string }) {
export async function installTypeModules(args: {
workDir: string;
moduleDir: string;
npmClient?: string;
}) {
const { workDir, moduleDir, npmClient = 'tnpm' } = args;
const pkgJsonPath = path.resolve(moduleDir, 'package.json');
if (!(await pathExists(pkgJsonPath))) {
@ -65,3 +84,19 @@ export function loadFile(filePath: string): string {
}
return content.toString();
}
export function isPrimitive(val) {
return !['object', 'function'].includes(typeof val) || val === null;
}
export function isEvaluable(value) {
if (isPrimitive(value)) return true;
if (Array.isArray(value)) {
return value.every(isEvaluable);
} else if (isPlainObject(value)) {
return Object.keys(value).every(key => isEvaluable(value[key]));
}
return false;
}
export { safeEval };

View File

@ -1,10 +1,11 @@
import Ajv from 'ajv';
import { Json } from '../types/Basic';
import schema from './schema.json';
const ajv = new Ajv({ jsonPointers: true });
const validate = ajv.compile(schema);
export default function validateSchema(json: object) {
export default function validateSchema(json: Json) {
if (validate(json) === false) {
throw new Error(JSON.stringify(validate.errors, null, 2));
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ Generated by [AVA](https://avajs.dev).
[
{
componentName: 'AIMakeBlank',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -63,6 +64,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'AIMakeIcon',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -112,6 +114,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'AIMakeImage',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -139,6 +142,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'AIMakeLink',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -187,6 +191,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'AIMakePlaceholder',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -221,6 +226,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'AIMakeText',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -273,6 +279,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'Root',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -300,6 +307,7 @@ Generated by [AVA](https://avajs.dev).
[
{
componentName: 'Demo',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: false,
@ -312,10 +320,7 @@ Generated by [AVA](https://avajs.dev).
props: [
{
name: 'optionalArray',
propType: {
type: 'instanceOf',
value: 'array',
},
propType: 'array',
},
{
name: 'optionalBool',
@ -344,24 +349,15 @@ Generated by [AVA](https://avajs.dev).
},
{
name: 'optionalNode',
propType: {
type: 'instanceOf',
value: 'node',
},
propType: 'node',
},
{
name: 'optionalElement',
propType: {
type: 'instanceOf',
value: 'element',
},
propType: 'element',
},
{
name: 'optionalElementType',
propType: {
type: 'instanceOf',
value: 'elementType',
},
propType: 'any',
},
{
name: 'optionalMessage',
@ -486,6 +482,7 @@ Generated by [AVA](https://avajs.dev).
[
{
componentName: 'default',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: false,
@ -498,70 +495,7 @@ Generated by [AVA](https://avajs.dev).
props: [
{
name: 'node',
propType: {
type: 'oneOfType',
value: [
'string',
'number',
false,
true,
'object',
{
type: 'shape',
value: [
{
name: 'P',
propType: 'any',
},
{
name: 'T',
propType: 'any',
},
{
name: 'type',
propType: 'any',
},
{
name: 'props',
propType: 'any',
},
{
name: 'key',
propType: {
type: 'oneOfType',
value: [
'string',
'number',
],
},
},
],
},
{
type: 'oneOfType',
value: [],
},
{
type: 'shape',
value: [
{
name: 'key',
propType: {
type: 'oneOfType',
value: [
'string',
'number',
],
},
},
{
name: 'children',
propType: 'node',
},
],
},
],
},
propType: 'node',
},
],
screenshot: '',
@ -569,6 +503,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'default',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: false,

View File

@ -4,199 +4,54 @@ The actual snapshot is saved in `online.ts.snap`.
Generated by [AVA](https://avajs.dev).
## materialize custom breadcrumb by online
## materialize mc-hello by online
> Snapshot 1
[
{
componentName: 'default',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: false,
exportName: 'default',
main: 'lib/index.js',
package: 'mc-breadcrumb',
package: 'mc-hello',
subName: '',
version: '1.0.1',
},
props: [
{
name: 'prefix',
name: 'color',
propType: 'string',
},
{
name: 'rtl',
name: 'background',
propType: 'string',
},
{
defaultValue: false,
name: 'round',
propType: 'bool',
},
{
defaultValue: 200,
name: 'width',
propType: 'number',
},
{
defaultValue: 40,
name: 'height',
propType: 'number',
},
{
name: 'children',
propType: {
type: 'instanceOf',
value: 'custom',
},
},
{
name: 'maxNode',
propType: {
type: 'oneOfType',
value: [
'number',
{
type: 'oneOf',
value: [
'auto',
],
},
],
},
},
{
name: 'separator',
propType: {
type: 'oneOfType',
value: [
{
type: 'instanceOf',
value: 'node',
},
'string',
],
},
},
{
name: 'component',
propType: {
type: 'oneOfType',
value: [
'string',
'func',
],
},
},
{
name: 'className',
propType: 'any',
},
{
name: 'locale',
propType: 'object',
},
{
name: 'pure',
propType: 'bool',
},
{
name: 'device',
propType: {
type: 'oneOf',
value: [
'tablet',
'desktop',
'phone',
],
},
},
{
name: 'popupContainer',
propType: 'any',
},
{
name: 'errorBoundary',
propType: {
type: 'oneOfType',
value: [
'bool',
'object',
],
},
propType: 'node',
},
],
screenshot: '',
title: 'mc-breadcrumb',
},
{
componentName: 'Item',
docUrl: '',
npm: {
destructuring: false,
exportName: 'default',
main: 'lib/index.js',
package: 'mc-breadcrumb',
subName: 'Item',
version: '1.0.1',
},
props: [
{
name: 'prefix',
propType: 'string',
},
{
name: 'rtl',
propType: 'bool',
},
{
name: 'link',
propType: 'string',
},
{
name: 'activated',
propType: 'bool',
},
{
name: 'separator',
propType: {
type: 'instanceOf',
value: 'node',
},
},
{
name: 'className',
propType: 'any',
},
{
name: 'children',
propType: {
type: 'instanceOf',
value: 'node',
},
},
{
name: 'locale',
propType: 'object',
},
{
name: 'pure',
propType: 'bool',
},
{
name: 'device',
propType: {
type: 'oneOf',
value: [
'tablet',
'desktop',
'phone',
],
},
},
{
name: 'popupContainer',
propType: 'any',
},
{
name: 'errorBoundary',
propType: {
type: 'oneOfType',
value: [
'bool',
'object',
],
},
},
],
screenshot: '',
title: 'mc-breadcrumb',
title: 'mc-hello',
},
]
@ -207,6 +62,7 @@ Generated by [AVA](https://avajs.dev).
[
{
componentName: 'BlockPicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -239,7 +95,6 @@ Generated by [AVA](https://avajs.dev).
},
},
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -249,6 +104,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'CirclePicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -281,7 +137,6 @@ Generated by [AVA](https://avajs.dev).
propType: 'number',
},
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -291,6 +146,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'default',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: false,
@ -318,7 +174,6 @@ Generated by [AVA](https://avajs.dev).
propType: 'bool',
},
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -339,6 +194,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'ChromePicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -366,7 +222,6 @@ Generated by [AVA](https://avajs.dev).
propType: 'bool',
},
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -387,6 +242,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'CompactPicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -398,7 +254,6 @@ Generated by [AVA](https://avajs.dev).
},
props: [
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -408,6 +263,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'GithubPicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -443,7 +299,6 @@ Generated by [AVA](https://avajs.dev).
},
},
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -453,6 +308,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'HuePicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -464,7 +320,6 @@ Generated by [AVA](https://avajs.dev).
},
props: [
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -474,6 +329,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'PhotoshopPicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -489,7 +345,6 @@ Generated by [AVA](https://avajs.dev).
propType: 'string',
},
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -499,6 +354,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'SketchPicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -526,7 +382,6 @@ Generated by [AVA](https://avajs.dev).
},
},
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -536,6 +391,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'SliderPicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -547,7 +403,6 @@ Generated by [AVA](https://avajs.dev).
},
props: [
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -557,6 +412,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'SwatchesPicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -590,7 +446,6 @@ Generated by [AVA](https://avajs.dev).
},
},
{
defaultValue: {},
name: 'styles',
propType: 'object',
},
@ -600,6 +455,7 @@ Generated by [AVA](https://avajs.dev).
},
{
componentName: 'TwitterPicker',
devMode: 'proCode',
docUrl: '',
npm: {
destructuring: true,
@ -633,7 +489,6 @@ Generated by [AVA](https://avajs.dev).
},
},
{
defaultValue: {},
name: 'styles',
propType: 'object',
},

View File

@ -29,7 +29,6 @@ interface Props {
node?: React.ReactNode;
// element?: JSX.Element;
// elementType?: React.ElementType;
// instance: Props;
}
const App = (props: Props) => {

View File

@ -39,4 +39,3 @@ test('ts component by local', async t => {
t.snapshot(actual);
});

View File

@ -3,7 +3,7 @@ import parse from '../src';
import { IMaterializeOptions } from '../src/types';
const reactColorComponent = 'react-color';
const customComponent = 'mc-breadcrumb';
const customComponent = 'mc-hello@1.0.1';
test('materialize react-color by online', async t => {
const options: IMaterializeOptions = {
@ -15,7 +15,7 @@ test('materialize react-color by online', async t => {
t.snapshot(actual);
});
test('materialize custom breadcrumb by online', async t => {
test('materialize mc-hello by online', async t => {
const options: IMaterializeOptions = {
entry: customComponent,
accesser: 'online',