From f82b574fd498910c87fbcf03c4fc15afd7acfa2e Mon Sep 17 00:00:00 2001 From: Joseph Frazier <1212jtraceur@gmail.com> Date: Mon, 18 Dec 2017 12:33:53 -0500 Subject: [PATCH] Return early from `massageAST()` (#3522) This reduces nesting and handles the edge cases first. This commit is best viewed ignoring whitespace differences: git show -w --- src/clean-ast.js | 510 ++++++++++++++++++++++++----------------------- 1 file changed, 256 insertions(+), 254 deletions(-) diff --git a/src/clean-ast.js b/src/clean-ast.js index 9ef279d7d..1913fb83c 100644 --- a/src/clean-ast.js +++ b/src/clean-ast.js @@ -8,262 +8,264 @@ function massageAST(ast, parent) { if (Array.isArray(ast)) { return ast.map(e => massageAST(e, parent)).filter(e => e); } - if (ast && typeof ast === "object") { - // We remove extra `;` and add them when needed - if (ast.type === "EmptyStatement") { - return undefined; - } - // We move text around, including whitespaces and add {" "} - if (ast.type === "JSXText") { - return undefined; - } - if ( - ast.type === "JSXExpressionContainer" && - ast.expression.type === "Literal" && - ast.expression.value === " " - ) { - return undefined; - } - - const newObj = {}; - for (const key in ast) { - if (typeof ast[key] !== "function") { - newObj[key] = massageAST(ast[key], ast); - } - } - - [ - "loc", - "range", - "raw", - "comments", - "leadingComments", - "trailingComments", - "extra", - "start", - "end", - "tokens", - "flags", - "raws", - "sourceIndex", - "id", - "source", - "before", - "after", - "trailingComma", - "parent", - "prev", - "position" - ].forEach(name => { - delete newObj[name]; - }); - - // for markdown codeblock - if (ast.type === "code") { - delete newObj.value; - } - // for markdown whitespace: "\n" and " " are considered the same - if (ast.type === "whitespace" && ast.value === "\n") { - newObj.value = " "; - } - - if ( - ast.type === "media-query" || - ast.type === "media-query-list" || - ast.type === "media-feature-expression" - ) { - delete newObj.value; - } - - if (ast.type === "css-rule") { - delete newObj.params; - } - - if (ast.type === "selector-combinator") { - newObj.value = newObj.value.replace(/\s+/g, " "); - } - - if (ast.type === "media-feature") { - newObj.value = newObj.value.replace(/ /g, ""); - } - - if ( - (ast.type === "value-word" && ast.isColor && ast.isHex) || - ast.type === "media-feature" || - ast.type === "selector-root-invalid" || - ast.type === "selector-pseudo" - ) { - newObj.value = newObj.value.toLowerCase(); - } - if (ast.type === "css-decl") { - newObj.prop = newObj.prop.toLowerCase(); - } - if (ast.type === "css-atrule" || ast.type === "css-import") { - newObj.name = newObj.name.toLowerCase(); - } - if (ast.type === "value-number") { - newObj.unit = newObj.unit.toLowerCase(); - } - - if ( - (ast.type === "media-feature" || - ast.type === "media-keyword" || - ast.type === "media-type" || - ast.type === "media-unknown" || - ast.type === "media-url" || - ast.type === "media-value" || - ast.type === "selector-root-invalid" || - ast.type === "selector-attribute" || - ast.type === "selector-string" || - ast.type === "selector-class" || - ast.type === "selector-combinator" || - ast.type === "value-string") && - newObj.value - ) { - newObj.value = cleanCSSStrings(newObj.value); - } - - if (ast.type === "css-import" && newObj.importPath) { - newObj.importPath = cleanCSSStrings(newObj.importPath); - } - - if (ast.type === "selector-attribute" && newObj.value) { - newObj.value = newObj.value.replace(/^['"]|['"]$/g, ""); - delete newObj.quoted; - } - - if ( - (ast.type === "media-value" || - ast.type === "media-type" || - ast.type === "value-number" || - ast.type === "selector-root-invalid" || - ast.type === "selector-class" || - ast.type === "selector-combinator" || - ast.type === "selector-tag") && - newObj.value - ) { - newObj.value = newObj.value.replace( - /([\d.eE+-]+)([a-zA-Z]*)/g, - (match, numStr, unit) => { - const num = Number(numStr); - return isNaN(num) ? match : num + unit.toLowerCase(); - } - ); - } - - // (TypeScript) Ignore `static` in `constructor(static p) {}` - // and `export` in `constructor(export p) {}` - if ( - ast.type === "TSParameterProperty" && - ast.accessibility === null && - !ast.readonly - ) { - return { - type: "Identifier", - name: ast.parameter.name, - typeAnnotation: newObj.parameter.typeAnnotation, - decorators: newObj.decorators - }; - } - - // (TypeScript) ignore empty `specifiers` array - if ( - ast.type === "TSNamespaceExportDeclaration" && - ast.specifiers && - ast.specifiers.length === 0 - ) { - delete newObj.specifiers; - } - - // (TypeScript) bypass TSParenthesizedType - if ( - ast.type === "TSParenthesizedType" && - ast.typeAnnotation.type === "TSTypeAnnotation" - ) { - return newObj.typeAnnotation.typeAnnotation; - } - - // We convert
to
- if (ast.type === "JSXOpeningElement") { - delete newObj.selfClosing; - } - if (ast.type === "JSXElement") { - delete newObj.closingElement; - } - - // We change {'key': value} into {key: value} - if ( - (ast.type === "Property" || - ast.type === "MethodDefinition" || - ast.type === "ClassProperty" || - ast.type === "TSPropertySignature" || - ast.type === "ObjectTypeProperty") && - typeof ast.key === "object" && - ast.key && - (ast.key.type === "Literal" || ast.key.type === "Identifier") - ) { - delete newObj.key; - } - - // Remove raw and cooked values from TemplateElement when it's CSS - // styled-jsx - if ( - ast.type === "JSXElement" && - ast.openingElement.name.name === "style" && - ast.openingElement.attributes.some(attr => attr.name.name === "jsx") - ) { - const templateLiterals = newObj.children - .filter( - child => - child.type === "JSXExpressionContainer" && - child.expression.type === "TemplateLiteral" - ) - .map(container => container.expression); - - const quasis = templateLiterals.reduce( - (quasis, templateLiteral) => quasis.concat(templateLiteral.quasis), - [] - ); - - quasis.forEach(q => delete q.value); - } - - // CSS template literals in css prop - if ( - ast.type === "JSXAttribute" && - ast.name.name === "css" && - ast.value.type === "JSXExpressionContainer" && - ast.value.expression.type === "TemplateLiteral" - ) { - newObj.value.expression.quasis.forEach(q => delete q.value); - } - - // styled-components, graphql, markdown - if ( - ast.type === "TaggedTemplateExpression" && - (ast.tag.type === "MemberExpression" || - (ast.tag.type === "Identifier" && - (ast.tag.name === "gql" || - ast.tag.name === "graphql" || - ast.tag.name === "css" || - ast.tag.name === "md" || - ast.tag.name === "markdown")) || - ast.tag.type === "CallExpression") - ) { - newObj.quasi.quasis.forEach(quasi => delete quasi.value); - } - if ( - ast.type === "TemplateLiteral" && - parent.type === "CallExpression" && - parent.callee.name === "graphql" - ) { - newObj.quasis.forEach(quasi => delete quasi.value); - } - - return newObj; + if (!ast || typeof ast !== "object") { + return ast; } - return ast; + + // We remove extra `;` and add them when needed + if (ast.type === "EmptyStatement") { + return undefined; + } + + // We move text around, including whitespaces and add {" "} + if (ast.type === "JSXText") { + return undefined; + } + if ( + ast.type === "JSXExpressionContainer" && + ast.expression.type === "Literal" && + ast.expression.value === " " + ) { + return undefined; + } + + const newObj = {}; + for (const key in ast) { + if (typeof ast[key] !== "function") { + newObj[key] = massageAST(ast[key], ast); + } + } + + [ + "loc", + "range", + "raw", + "comments", + "leadingComments", + "trailingComments", + "extra", + "start", + "end", + "tokens", + "flags", + "raws", + "sourceIndex", + "id", + "source", + "before", + "after", + "trailingComma", + "parent", + "prev", + "position" + ].forEach(name => { + delete newObj[name]; + }); + + // for markdown codeblock + if (ast.type === "code") { + delete newObj.value; + } + // for markdown whitespace: "\n" and " " are considered the same + if (ast.type === "whitespace" && ast.value === "\n") { + newObj.value = " "; + } + + if ( + ast.type === "media-query" || + ast.type === "media-query-list" || + ast.type === "media-feature-expression" + ) { + delete newObj.value; + } + + if (ast.type === "css-rule") { + delete newObj.params; + } + + if (ast.type === "selector-combinator") { + newObj.value = newObj.value.replace(/\s+/g, " "); + } + + if (ast.type === "media-feature") { + newObj.value = newObj.value.replace(/ /g, ""); + } + + if ( + (ast.type === "value-word" && ast.isColor && ast.isHex) || + ast.type === "media-feature" || + ast.type === "selector-root-invalid" || + ast.type === "selector-pseudo" + ) { + newObj.value = newObj.value.toLowerCase(); + } + if (ast.type === "css-decl") { + newObj.prop = newObj.prop.toLowerCase(); + } + if (ast.type === "css-atrule" || ast.type === "css-import") { + newObj.name = newObj.name.toLowerCase(); + } + if (ast.type === "value-number") { + newObj.unit = newObj.unit.toLowerCase(); + } + + if ( + (ast.type === "media-feature" || + ast.type === "media-keyword" || + ast.type === "media-type" || + ast.type === "media-unknown" || + ast.type === "media-url" || + ast.type === "media-value" || + ast.type === "selector-root-invalid" || + ast.type === "selector-attribute" || + ast.type === "selector-string" || + ast.type === "selector-class" || + ast.type === "selector-combinator" || + ast.type === "value-string") && + newObj.value + ) { + newObj.value = cleanCSSStrings(newObj.value); + } + + if (ast.type === "css-import" && newObj.importPath) { + newObj.importPath = cleanCSSStrings(newObj.importPath); + } + + if (ast.type === "selector-attribute" && newObj.value) { + newObj.value = newObj.value.replace(/^['"]|['"]$/g, ""); + delete newObj.quoted; + } + + if ( + (ast.type === "media-value" || + ast.type === "media-type" || + ast.type === "value-number" || + ast.type === "selector-root-invalid" || + ast.type === "selector-class" || + ast.type === "selector-combinator" || + ast.type === "selector-tag") && + newObj.value + ) { + newObj.value = newObj.value.replace( + /([\d.eE+-]+)([a-zA-Z]*)/g, + (match, numStr, unit) => { + const num = Number(numStr); + return isNaN(num) ? match : num + unit.toLowerCase(); + } + ); + } + + // (TypeScript) Ignore `static` in `constructor(static p) {}` + // and `export` in `constructor(export p) {}` + if ( + ast.type === "TSParameterProperty" && + ast.accessibility === null && + !ast.readonly + ) { + return { + type: "Identifier", + name: ast.parameter.name, + typeAnnotation: newObj.parameter.typeAnnotation, + decorators: newObj.decorators + }; + } + + // (TypeScript) ignore empty `specifiers` array + if ( + ast.type === "TSNamespaceExportDeclaration" && + ast.specifiers && + ast.specifiers.length === 0 + ) { + delete newObj.specifiers; + } + + // (TypeScript) bypass TSParenthesizedType + if ( + ast.type === "TSParenthesizedType" && + ast.typeAnnotation.type === "TSTypeAnnotation" + ) { + return newObj.typeAnnotation.typeAnnotation; + } + + // We convert
to
+ if (ast.type === "JSXOpeningElement") { + delete newObj.selfClosing; + } + if (ast.type === "JSXElement") { + delete newObj.closingElement; + } + + // We change {'key': value} into {key: value} + if ( + (ast.type === "Property" || + ast.type === "MethodDefinition" || + ast.type === "ClassProperty" || + ast.type === "TSPropertySignature" || + ast.type === "ObjectTypeProperty") && + typeof ast.key === "object" && + ast.key && + (ast.key.type === "Literal" || ast.key.type === "Identifier") + ) { + delete newObj.key; + } + + // Remove raw and cooked values from TemplateElement when it's CSS + // styled-jsx + if ( + ast.type === "JSXElement" && + ast.openingElement.name.name === "style" && + ast.openingElement.attributes.some(attr => attr.name.name === "jsx") + ) { + const templateLiterals = newObj.children + .filter( + child => + child.type === "JSXExpressionContainer" && + child.expression.type === "TemplateLiteral" + ) + .map(container => container.expression); + + const quasis = templateLiterals.reduce( + (quasis, templateLiteral) => quasis.concat(templateLiteral.quasis), + [] + ); + + quasis.forEach(q => delete q.value); + } + + // CSS template literals in css prop + if ( + ast.type === "JSXAttribute" && + ast.name.name === "css" && + ast.value.type === "JSXExpressionContainer" && + ast.value.expression.type === "TemplateLiteral" + ) { + newObj.value.expression.quasis.forEach(q => delete q.value); + } + + // styled-components, graphql, markdown + if ( + ast.type === "TaggedTemplateExpression" && + (ast.tag.type === "MemberExpression" || + (ast.tag.type === "Identifier" && + (ast.tag.name === "gql" || + ast.tag.name === "graphql" || + ast.tag.name === "css" || + ast.tag.name === "md" || + ast.tag.name === "markdown")) || + ast.tag.type === "CallExpression") + ) { + newObj.quasi.quasis.forEach(quasi => delete quasi.value); + } + if ( + ast.type === "TemplateLiteral" && + parent.type === "CallExpression" && + parent.callee.name === "graphql" + ) { + newObj.quasis.forEach(quasi => delete quasi.value); + } + + return newObj; } function cleanCSSStrings(value) {