Skip to content

Commit

Permalink
TS: Collec props in object spread
Browse files Browse the repository at this point in the history
Ref #11
  • Loading branch information
nolimits4web committed Aug 31, 2018
1 parent b33e9cd commit fefd555
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 34 deletions.
4 changes: 2 additions & 2 deletions lib/compiler-generator/generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const porcessReplaceComponentNode = require('./process-replace-component-node');
const processExports = require('./process-exports');

function generator(jsxTransformer, componentTransformer, typescriptGenerator) {
function generate(componentString, config, output) {
function generate(componentString, config, input, output) {
const state = new CompilerState(config, output);

let modifiedComponentString = componentString;
Expand All @@ -36,7 +36,7 @@ function generator(jsxTransformer, componentTransformer, typescriptGenerator) {
const { helpers: jsxHelpers } = jsxTransformer({ ast, name, functional, componentNode, state, config });
componentTransformer({ ast, name, functional, componentNode, state, config, jsxHelpers });
if (config.typeScriptDefinitions && typescriptGenerator) {
typescriptGenerator({ ast, name, functional, componentNode, state, config });
typescriptGenerator({ ast, name, functional, componentNode, state, config, input });
}

processDeclarations(ast, state.declarations);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const compileAllFiles = (compilerName, filesToProcess, config, compiler) => {
basePath: outputBase,
filePath: outputPath,
};
const compilerOutput = compiler(file.contents, config, output);
const compilerOutput = compiler(file.contents, config, file, output);

if (compilerOutput.transformed && path.extname(output.filePath) === '.jsx') {
outputPath = `${outputPath.substr(0, outputPath.lastIndexOf('.'))}.js`;
Expand Down
76 changes: 76 additions & 0 deletions lib/compiler-utils/find-object-spread-properties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const path = require('path');
const fs = require('fs');
const walk = require('./walk');
const codeToAst = require('./code-to-ast');

function searchPropsInVariableDeclaration(ast, declarationName) {
let foundProps;
walk(ast, {
VariableDeclaration(node) {
node.declarations.forEach((declaration) => {
if (declaration.id && declaration.id.name === declarationName && declaration.init && declaration.init.properties) {
foundProps = declaration.init.properties;
}
});
},
});
return foundProps;
}

function findObjectSpreadProperties(ast, declarationName, filePath) {
let objProps;
const objName = declarationName.split('.')[0];
const objPath = declarationName.split('.').filter((el, index) => index > 0);

let importPath;
let isExternal;
let importDefault;
ast.body.forEach((node) => {
if (node.type === 'ImportDeclaration') {
node.specifiers.forEach((specifier) => {
if (specifier.local && specifier.local.name === objName) {
importPath = path.join(path.dirname(filePath), node.source.value);
if (importPath.indexOf('.js') < 0) importPath += '.js';
isExternal = true;
if (specifier.type === 'ImportDefaultSpecifier') importDefault = true;
}
});
}
});

if (isExternal && importPath) {
const depFileContent = fs.readFileSync(importPath, 'utf8');
const depFileAst = codeToAst(depFileContent);

if (importDefault) {
walk(depFileAst, {
ExportDefaultDeclaration(node) {
if (node.declaration.type === 'ObjectExpression') {
objProps = node.declaration.properties;
} else if (node.declaration.type === 'Identifier') {
objProps = searchPropsInVariableDeclaration(depFileAst, node.declaration.name);
}
},
});
} else {
objProps = searchPropsInVariableDeclaration(depFileAst, objName);
}
}

if (!isExternal) {
objProps = searchPropsInVariableDeclaration(ast, objName);
}

if (objProps && objPath) {
objPath.forEach((pathPart) => {
objProps.forEach((objProp) => {
if (objProp.key && objProp.key.name === pathPart && objProp.value.type === 'ObjectExpression') {
objProps = objProp.value.properties;
}
});
});
}
return objProps;
}

module.exports = findObjectSpreadProperties;
81 changes: 51 additions & 30 deletions lib/compilers/react/typescript-generator/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const toCamelCase = require('../../../compiler-utils/to-camel-case');
const astToCode = require('../../../compiler-utils/ast-to-code');
const walk = require('../../../compiler-utils/walk');
const findObjectSpreadProperties = require('../../../compiler-utils/find-object-spread-properties');
const traversePhenomeComponent = require('../../../compiler-utils/traverse-phenome-component');

const dtsTemplate = `
Expand Down Expand Up @@ -32,7 +33,7 @@ function typeMap(type) {
return 'any';
}

function collectProps(componentNode) {
function collectProps(componentNode, ast, input) {
const props = [{
name: 'slot',
type: 'String',
Expand All @@ -44,39 +45,59 @@ function collectProps(componentNode) {
if (node.type === 'MemberExpression') return astToCode(node, { format: { compact: true } });
return 'any';
}

function getProp(propNode) {
const prop = {
name: propNode.key.name,
};
if (propNode.value.type === 'Identifier' || propNode.value.type === 'MemberExpression') {
/* foo: String */
prop.type = propNode.value.name;
prop.type = getPropType(propNode.value);
prop.required = false;
} else if (propNode.value.type === 'ArrayExpression') {
/* foo: [String, Number] */
prop.type = propNode.value.elements.map(el => el.name);
prop.type = getPropType(propNode.value);
prop.required = false;
} else if (propNode.value.type === 'ObjectExpression') {
/* foo: { type: String, default: 'bar', required: true } */
propNode.value.properties.forEach((subProp) => {
if (subProp.key.name === 'type') {
prop.type = getPropType(subProp.value);
}
if (subProp.key.name === 'default') {
prop.default = astToCode(subProp.value, { format: { compact: true } });
}
if (subProp.key.name === 'required') {
prop.required = subProp.value.value;
}
});
}
return prop;
}
traversePhenomeComponent(componentNode, {
props(node) {
node.value.properties.forEach((propNode) => {
if (propNode.type !== 'Property') return;
const prop = {
name: propNode.key.name,
};
if (propNode.value.type === 'Identifier' || propNode.value.type === 'MemberExpression') {
/* foo: String */
prop.type = propNode.value.name;
prop.type = getPropType(propNode.value);
prop.required = false;
} else if (propNode.value.type === 'ArrayExpression') {
/* foo: [String, Number] */
prop.type = propNode.value.elements.map(el => el.name);
prop.type = getPropType(propNode.value);
prop.required = false;
} else if (propNode.value.type === 'ObjectExpression') {
/* foo: { type: String, default: 'bar', required: true } */
propNode.value.properties.forEach((subProp) => {
if (subProp.key.name === 'type') {
prop.type = getPropType(subProp.value);
}
if (subProp.key.name === 'default') {
prop.default = astToCode(subProp.value, { format: { compact: true } });
if (propNode.type !== 'Property') {
if (propNode.type === 'SpreadElement') {
let objProps;
if (propNode.argument.type === 'Identifier') {
objProps = findObjectSpreadProperties(ast, propNode.argument.name, input.fullPath);
} else if (propNode.argument.type === 'MemberExpression') {
objProps = findObjectSpreadProperties(ast, astToCode(propNode.argument), input.fullPath);
}
if (subProp.key.name === 'required') {
prop.required = subProp.value.value;
if (objProps) {
objProps.forEach((objPropNode) => {
const prop = getProp(objPropNode);
if (prop) props.push(prop);
});
}
});
}
return;
}

props.push(prop);
const prop = getProp(propNode);
if (prop) props.push(prop);
});
},
});
Expand Down Expand Up @@ -184,9 +205,9 @@ function renderMethods(methods, indent) {
.join(`\n${indent}`);
}

const generate = ({ name = 'MyComponent', componentNode, state }) => {
const generate = ({ name = 'MyComponent', componentNode, state, ast, input }) => {
const camelCaseName = toCamelCase(name);
const props = collectProps(componentNode);
const props = collectProps(componentNode, ast, input);
const events = collectEvents(componentNode);
const methods = collectMethods(componentNode);
const renderedProps = renderProps(props, ' ');
Expand Down
17 changes: 16 additions & 1 deletion test-component/src/component.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
import Mixins from './mixins';

const moreProps = {
moreFoo: String,
};
const deepProps = {
moreDeepProps: {
eventMoreDeepProps: {
superDeepFoo: String,
},
}
}

export default {
name: 'my-phenome-component',
props: {
Expand All @@ -10,7 +23,9 @@ export default {
required: true,
},
two: window.FormData,
dt: Date,
date: Date,
...moreProps,
...deepProps.moreDeepProps.eventMoreDeepProps,
...Mixins.colorProps,
},
methods: {
Expand Down
12 changes: 12 additions & 0 deletions test-component/src/mixins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const Mixins = {
colorProps: {
color: String,
colorTheme: String,
textColor: String,
bgColor: String,
borderColor: String,
rippleColor: String,
themeDark: Boolean,
},
}
export default Mixins;

0 comments on commit fefd555

Please sign in to comment.