Esempio n. 1
0
// Given a NodePath for a Function, return an Expression node that can be
// used to refer reliably to the function object from inside the function.
// This expression is essentially a replacement for arguments.callee, with
// the key advantage that it works in strict mode.
function getOuterFnExpr(funPath) {
  let node = funPath.node;
  t.assertFunction(node);

  if (node.generator && // Non-generator functions don't need to be marked.
      t.isFunctionDeclaration(node)) {
    let pp = funPath.findParent(function (path) {
      return path.isProgram() || path.isBlockStatement();
    });

    if (!pp) {
      return node.id;
    }

    let markDecl = getRuntimeMarkDecl(pp);
    let markedArray = markDecl.declarations[0].id;
    let funDeclIdArray = markDecl.declarations[0].init.callee.object;
    t.assertArrayExpression(funDeclIdArray);

    let index = funDeclIdArray.elements.length;
    funDeclIdArray.elements.push(node.id);

    return t.memberExpression(
      markedArray,
      t.numericLiteral(index),
      true
    );
  }

  return node.id || (
    node.id = funPath.scope.parent.generateUidIdentifier("callee")
  );
}
		it("returns a NumberType for a number literal", function () {
			// arrange
			const nullLiteral = t.numericLiteral(3);

			// act, assert
			expect(rule.refine(nullLiteral)).to.be.instanceOf(NumberType);
		});
Esempio n. 3
0
File: index.js Progetto: ANWSY/babel
 buildUndefinedNode() {
   if (this.hasBinding("undefined")) {
     return t.unaryExpression("void", t.numericLiteral(0), true);
   } else {
     return t.identifier("undefined");
   }
 }
		it ("returns true for a number literal", function () {
			// arrange
			const numberLiteral = t.numericLiteral(10);

			// act, assert
			expect(rule.canRefine(numberLiteral)).to.be.true;
		});
				it("detects recursive calls with the same arguments and uses the return type of the previously called function", function () {
					// arrange
					const func = functionDeclaration(t.identifier("successor"), t.identifier("x"));
					const x = new Symbol("x", SymbolFlags.Variable);
					program.symbolTable.setSymbol(func.params[0], x);

					const funcT = new FunctionType(new TypeVariable(), [], VoidType.create(), func);

					const call = t.callExpression(t.identifier("successor"), [t.numericLiteral(4)]);
					typeInferenceAnalysis.infer.withArgs(call.callee).returns(funcT);

					let analyseCount = 0;
					typeInferenceAnalysis.analyse = (node, typeEnvironment) => {
						++analyseCount;
						const recursiveCall = t.callExpression(t.identifier("successor"), [t.binaryExpression("-", t.identifier("x"), t.numericLiteral(-1))]);
						typeInferenceAnalysis.infer.withArgs(recursiveCall.callee).returns(funcT);
						program.symbolTable.setSymbol(recursiveCall.arguments[0].left, x);
						rule.refine(recursiveCall, context);

						return new Map([[null, typeEnvironment.setType(Symbol.RETURN, NumberType.create())]]);
					};

					typeInferenceAnalysis.infer.returns(NumberType.create());

					// act
					expect(rule.refine(call, context)).to.be.instanceOf(NumberType);
					expect(analyseCount).to.equals(1);
				});
Esempio n. 6
0
 exit: function (node, parent, index) {
   if (isNumeric(node.body.value)) {
     parent[index] = { key: node.label, value: t.numericLiteral(Number(node.body.value)) };
   } else {
     parent[index] = { key: node.label, value: node.body };
   }
 }
export function makeLiteral(value) {
  if (typeof value === 'string') return t.stringLiteral(value);
  else if (typeof value === 'number') return t.numericLiteral(value);
  else if (typeof value === 'boolean') return t.booleanLiteral(value);
  else {
    $debug('Encountered invalid literal', value);
    throw new TypeError(`Invalid type supplied, this is a bug in ${PLUGIN_NAME}, typeof is ${typeof value} with value ${value}`);
  }
}
Esempio n. 8
0
 // push a default parameter definition
 function pushDefNode(left, right, i) {
   const defNode = buildDefaultParam({
     VARIABLE_NAME: left,
     DEFAULT_VALUE: right,
     ARGUMENT_KEY:  t.numericLiteral(i),
     ARGUMENTS:     argsIdentifier
   });
   defNode._blockHoist = node.params.length - i;
   body.push(defNode);
 }
Esempio n. 9
0
Ep.getDispatchLoop = function() {
  let self = this;
  let cases = [];
  let current;

  // If we encounter a break, continue, or return statement in a switch
  // case, we can skip the rest of the statements until the next case.
  let alreadyEnded = false;

  self.listing.forEach(function(stmt, i) {
    if (self.marked.hasOwnProperty(i)) {
      cases.push(t.switchCase(
        t.numericLiteral(i),
        current = []));
      alreadyEnded = false;
    }

    if (!alreadyEnded) {
      current.push(stmt);
      if (t.isCompletionStatement(stmt))
        alreadyEnded = true;
    }
  });

  // Now that we know how many statements there will be in this.listing,
  // we can finally resolve this.finalLoc.value.
  this.finalLoc.value = this.listing.length;

  cases.push(
    t.switchCase(this.finalLoc, [
      // Intentionally fall through to the "end" case...
    ]),

    // So that the runtime can jump to the final location without having
    // to know its offset, we provide the "end" case as a synonym.
    t.switchCase(t.stringLiteral("end"), [
      // This will check/clear both context.thrown and context.rval.
      t.returnStatement(
        t.callExpression(this.contextProperty("stop"), [])
      )
    ])
  );

  return t.whileStatement(
    t.numericLiteral(1),
    t.switchStatement(
      t.assignmentExpression(
        "=",
        this.contextProperty("prev"),
        this.contextProperty("next")
      ),
      cases
    )
  );
};
	beforeEach(function () {
		program = new Program();
		context = new HindleyMilnerContext(null, new TypeInferenceContext(program));

		sandbox = sinon.sandbox.create();
		sandbox.stub(context, "unify");
		sandbox.stub(context, "infer");

		rule = new AssignmentExpressionRefinementRule();
		assignmentExpression = t.assignmentExpression("=", t.identifier("x"), t.numericLiteral(5));
	});
			it("can call a function with missing optional arguments", function () {
				// arrange
				const substringType = new FunctionType(VoidType.create(), [NumberType.create(), MaybeType.of(NumberType.create())], StringType.create());
				const callExpression = t.callExpression(t.identifier("substring"), [t.numericLiteral(5)]);

				typeInferenceAnalysis.infer.withArgs(callExpression.callee).returns(substringType);
				typeInferenceAnalysis.infer.withArgs(callExpression.arguments[0]).returns(NumberType.create());
				typeInferenceAnalysis.unify.withArgs(sinon.match.instanceOf(NumberType), sinon.match.instanceOf(NumberType)).returns(NumberType.create());

				// act, assert
				expect(rule.refine(callExpression, context)).to.be.instanceOf(StringType);
			});
Esempio n. 12
0
File: rest.js Progetto: ANWSY/babel
function optimiseLengthGetter(path, argsLengthExpression, argsId, offset) {
  if (offset) {
    path.parentPath.replaceWith(
      t.binaryExpression(
        "-",
        argsLengthExpression,
        t.numericLiteral(offset),
      )
    );
  } else {
    path.replaceWith(argsId);
  }
}
Esempio n. 13
0
File: index.js Progetto: ANWSY/babel
  specHandle(path: NodePath) {
    let property;
    let computed;
    let args;
    let thisReference;

    let parent = path.parent;
    let node = path.node;

    if (isIllegalBareSuper(node, parent)) {
      throw path.buildCodeFrameError(messages.get("classesIllegalBareSuper"));
    }

    if (t.isCallExpression(node)) {
      let callee = node.callee;
      if (t.isSuper(callee)) {
        return;
      } else if (isMemberExpressionSuper(callee)) {
        // super.test(); -> _get(Object.getPrototypeOf(objectRef.prototype), "test", this).call(this);
        property = callee.property;
        computed = callee.computed;
        args = node.arguments;
      }
    } else if (t.isMemberExpression(node) && t.isSuper(node.object)) {
      // super.name; -> _get(Object.getPrototypeOf(objectRef.prototype), "name", this);
      property = node.property;
      computed = node.computed;
    } else if (t.isUpdateExpression(node) && isMemberExpressionSuper(node.argument)) {
      let binary = t.binaryExpression(node.operator[0], node.argument, t.numericLiteral(1));
      if (node.prefix) {
        // ++super.foo; -> super.foo += 1;
        return this.specHandleAssignmentExpression(null, path, binary);
      } else {
        // super.foo++; -> let _ref = super.foo; super.foo = _ref + 1;
        let ref = path.scope.generateUidIdentifier("ref");
        return this.specHandleAssignmentExpression(ref, path, binary).concat(t.expressionStatement(ref));
      }
    } else if (t.isAssignmentExpression(node) && isMemberExpressionSuper(node.left)) {
      return this.specHandleAssignmentExpression(null, path, node);
    }

    if (!property) return;

    let superProperty = this.getSuperProperty(property, computed, thisReference);

    if (args) {
      return this.optimiseCall(superProperty, args);
    } else {
      return superProperty;
    }
  }
Esempio n. 14
0
  self.listing.forEach(function(stmt, i) {
    if (self.marked.hasOwnProperty(i)) {
      cases.push(t.switchCase(
        t.numericLiteral(i),
        current = []));
      alreadyEnded = false;
    }

    if (!alreadyEnded) {
      current.push(stmt);
      if (t.isCompletionStatement(stmt))
        alreadyEnded = true;
    }
  });
Esempio n. 15
0
      node.attributes.forEach(attr => {
        if (t.isJSXSpreadAttribute(attr)) {
          if (canEvaluate(staticNamespace, attr.argument)) {
            const spreadValue = vm.runInContext(
              generate(attr.argument).code,
              evalContext
            );

            if (typeof spreadValue !== 'object' || spreadValue === null) {
              lastSpreadIndex = flattenedAttributes.push(attr) - 1;
            } else {
              for (const k in spreadValue) {
                const value = spreadValue[k];

                if (typeof value === 'number') {
                  flattenedAttributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(k),
                      t.jSXExpressionContainer(t.numericLiteral(value))
                    )
                  );
                } else if (value === null) {
                  // why would you ever do this
                  flattenedAttributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(k),
                      t.jSXExpressionContainer(t.nullLiteral())
                    )
                  );
                } else {
                  // toString anything else
                  // TODO: is this a bad idea
                  flattenedAttributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(k),
                      t.jSXExpressionContainer(t.stringLiteral('' + value))
                    )
                  );
                }
              }
            }
          } else {
            lastSpreadIndex = flattenedAttributes.push(attr) - 1;
          }
        } else {
          flattenedAttributes.push(attr);
        }
      });
Esempio n. 16
0
  test("nested if statements needs block", function() {
    var ifStatement = t.ifStatement(
      t.stringLiteral("top cond"),
      t.whileStatement(
        t.stringLiteral("while cond"),
        t.ifStatement(
          t.stringLiteral("nested"),
          t.expressionStatement(t.numericLiteral(1))
        )
      ),
      t.expressionStatement(t.stringLiteral("alt"))
    );

    var ast = parse(generate.default(ifStatement).code);
    assert.equal(ast.program.body[0].consequent.type, 'BlockStatement');
  });
Esempio n. 17
0
function addImport(asset, path) {
  // Replace with a $parcel$require call so we know where to insert side effects.
  let requireCall = REQUIRE_CALL_TEMPLATE({
    ID: t.numericLiteral(asset.id),
    SOURCE: t.stringLiteral(path.node.source.value)
  });

  // Hoist the call to the top of the file.
  let lastImport = path.scope.getData('hoistedImport');
  if (lastImport) {
    [lastImport] = lastImport.insertAfter(requireCall);
  } else {
    [lastImport] = path.parentPath.unshiftContainer('body', [requireCall]);
  }

  path.scope.setData('hoistedImport', lastImport);
}
Esempio n. 18
0
 // push a default parameter definition
 function pushDefNode(left, right, i) {
   let defNode;
   if (exceedsLastNonDefault(i) || t.isPattern(left)) {
     defNode = buildDefaultParam({
       VARIABLE_NAME: left,
       DEFAULT_VALUE: right,
       ARGUMENT_KEY:  t.numericLiteral(i),
       ARGUMENTS:     argsIdentifier
     });
   } else {
     defNode = buildDefaultParamAssign({
       VARIABLE_NAME: left,
       DEFAULT_VALUE: right
     });
   }
   defNode._blockHoist = node.params.length - i;
   body.push(defNode);
 }
			it("uses the binary operator with the given name to refine the type", function () {
				// arrange
				const plusAssignment = t.assignmentExpression("+=", t.identifier("x"), t.numericLiteral(4));
				const xType = NullType.create();
				const numberType = NumberType.create();
				program.symbolTable.setSymbol(plusAssignment.left, new Symbol("x", SymbolFlags.Variable));

				sandbox.stub(BINARY_OPERATORS["+"], "refine").returns(NumberType.create());

				context.infer.withArgs(plusAssignment.left).returns(xType);
				context.infer.withArgs(plusAssignment.right).returns(numberType);

				// act
				const refined = rule.refine(plusAssignment, context);

				// assert
				sinon.assert.calledWithExactly(BINARY_OPERATORS["+"].refine, xType, numberType, sinon.match.func);
				expect(refined).to.be.instanceOf(NumberType);
			});
Esempio n. 20
0
 return arr.map(value => {
   if (typeof value === 'string') {
     return t.stringLiteral(value)
   }
   if (typeof value === 'number') {
     return t.numericLiteral(value)
   }
   if (typeof value === 'boolean') {
     return t.booleanLiteral(value)
   }
   if (Array.isArray(value)) {
     return convertArrayToAstExpression(value)
   }
   if (value == null) {
     return t.nullLiteral()
   }
   if (typeof value === 'object') {
     return convertObjectToAstExpression(value)
   }
 })
			it("sets the (updated) type for the assignee in the type environment", function () {
				// arrange
				const plusAssignment = t.assignmentExpression("+=", t.identifier("x"), t.numericLiteral(4));
				const x = new Symbol("x", SymbolFlags.Variable);

				program.symbolTable.setSymbol(plusAssignment.left, x);

				const xType = NullType.create();
				const numberType = NumberType.create();

				context.infer.withArgs(plusAssignment.left).returns(xType);
				context.infer.withArgs(plusAssignment.right).returns(numberType);
				sandbox.stub(BINARY_OPERATORS["+"], "refine").returns(numberType);

				// act
				rule.refine(plusAssignment, context);

				// assert
				expect(context.getType(x)).to.be.instanceOf(NumberType);
			});
Esempio n. 22
0
  traverse(ast, function(node) {
    if (!node) {
      return false
    }

    if (node.type === 'CallExpression') {
      const name = getName(node.callee)
      if (name === 'require' || name === 'require.resolve') {
        if (name === 'require') {
          node.callee.name = '__require'
        } else {
          node.callee.object.name = '__require'
        }
        const argument = node.arguments[0]
        if (argument && argument.value) {
          promises.push(pundle.resolver.resolve(argument.value, filePath).then(function(resolved) {
            const resolvedFilePath = pundle.path.in(resolved)
            argument.value = pundle.getUniquePathID(resolvedFilePath)
            imports.add(resolvedFilePath)
          }))
        }
      }
    } else if (node.type === 'ImportDeclaration') {
      promises.push(pundle.resolver.resolve(node.source.value, filePath).then(function(resolved) {
        const resolvedFilePath = pundle.path.in(resolved)
        node.source.value = pundle.getUniquePathID(resolvedFilePath)
        imports.add(resolvedFilePath)
      }))
    } else if (node.type === 'MemberExpression') {
      const name = getName(node)
      const value = pundle.config.replaceVariables[name]
      if (typeof value === 'string') {
        return t.stringLiteral(value)
      } else if (typeof value === 'number' && value < Infinity && !Number.isNaN(value)) {
        return t.numericLiteral(value)
      } else if (typeof value !== 'undefined') {
        throw new Error(`Unknown replacement value for '${name}'`)
      }
    }
    return false
  })
Esempio n. 23
0
 const objArr = Object.keys(obj).map(key => {
   const value = obj[key]
   if (typeof value === 'string') {
     return t.objectProperty(t.stringLiteral(key), t.stringLiteral(value))
   }
   if (typeof value === 'number') {
     return t.objectProperty(t.stringLiteral(key), t.numericLiteral(value))
   }
   if (typeof value === 'boolean') {
     return t.objectProperty(t.stringLiteral(key), t.booleanLiteral(value))
   }
   if (Array.isArray(value)) {
     return t.objectProperty(t.stringLiteral(key), convertArrayToAstExpression(value))
   }
   if (value == null) {
     return t.objectProperty(t.stringLiteral(key), t.nullLiteral())
   }
   if (typeof value === 'object') {
     return t.objectProperty(t.stringLiteral(key), convertObjectToAstExpression(value))
   }
 })
Esempio n. 24
0
  toArray(node: Object, i?: number) {
    let file = this.hub.file;

    if (t.isIdentifier(node)) {
      let binding = this.getBinding(node.name);
      if (binding && binding.constant && binding.path.isGenericType("Array")) return node;
    }

    if (t.isArrayExpression(node)) {
      return node;
    }

    if (t.isIdentifier(node, { name: "arguments" })) {
      return t.callExpression(
        t.memberExpression(
          t.memberExpression(
            t.memberExpression(
              t.identifier("Array"),
              t.identifier("prototype")
            ),
            t.identifier("slice")
          ),
          t.identifier("call")
        ),
        [node]
      );
    }

    let helperName = "toArray";
    let args = [node];
    if (i === true) {
      helperName = "toConsumableArray";
    } else if (i) {
      args.push(t.numericLiteral(i));
      helperName = "slicedToArray";
      // TODO if (this.hub.file.isLoose("es6.forOf")) helperName += "-loose";
    }
    return t.callExpression(file.addHelper(helperName), args);
  }
				it("terminates recursive calls after 20 rounds", function () {
					this.timeout(5000);

					// arrange
					const func = functionDeclaration(t.identifier("f"));

					const c1 = t.callExpression(t.identifier("f"), []);
					const calls = [];
					let args = [];

					// create an array with 1000 call expressions. It simulates a function where the body always adds one more
					// argument and calls itself again.
					for (let i = 0; i < 1000; ++i) {
						args = args.concat(t.numericLiteral(i));
						calls.push(t.callExpression(t.identifier("f"), args));
					}

					const functionType = new FunctionType(new TypeVariable(), [], VoidType.create(), func);
					for (const call of calls.concat(c1)) {
						typeInferenceAnalysis.infer.withArgs(call.callee).returns(functionType);
					}

					let nextCall = 0;
					typeInferenceAnalysis.analyse = (node, typeEnvironment) => {
						if (nextCall > 20) {
							expect.fail("Recursive function is called more then twenty times, should terminate after 20 calls");
						}

						rule.refine(calls[nextCall++], context);

						return new Map([[null, typeEnvironment]]);
					};

					typeInferenceAnalysis.infer.returns(NumberType.create());

					// act
					rule.refine(c1, context);
				});
Esempio n. 26
0
File: rest.js Progetto: ANWSY/babel
            break;
          default:
            path.replaceWith(argsId);
        }
      }
      return;
    }

    state.references = state.references.concat(
      state.candidates.map(({ path }) => path)
    );

    // deopt shadowed functions as transforms like regenerator may try touch the allocation loop
    state.deopted = state.deopted || !!node.shadow;

    let start = t.numericLiteral(node.params.length);
    let key = scope.generateUidIdentifier("key");
    let len = scope.generateUidIdentifier("len");

    let arrKey = key;
    let arrLen = len;
    if (node.params.length) {
      // this method has additional params, so we need to subtract
      // the index of the current argument position from the
      // position in the array that we want to populate
      arrKey = t.binaryExpression("-", key, start);

      // we need to work out the size of the array that we're
      // going to store all the rest parameters
      //
      // we need to add a check to avoid constructing the array
Esempio n. 27
0
 test("numeric member expression", function() {
   // Should not generate `0.foo`
   var mem = t.memberExpression(t.numericLiteral(60702), t.identifier("foo"));
   new Function(generate.default(mem).code);
 });
Esempio n. 28
0
          // the right hand side references a parameter
          state.iife = true;
        } else {
          right.traverse(iifeVisitor, state);
        }
      }

      pushDefNode(left.node, right.node, i);
    }

    // add declarations for trailing parameters
    for (let i = lastNonDefaultParam + 1; i < node.params.length; i++) {
      const param = node.params[i];
      if (param._isDefaultPlaceholder) continue;

      const declar = buildCutOff(param, argsIdentifier, t.numericLiteral(i));
      declar._blockHoist = node.params.length - i;
      body.push(declar);
    }

    // we need to cut off all trailing parameters
    node.params = node.params.slice(0, lastNonDefaultParam);

    if (state.iife) {
      body.push(callDelegate(path, scope));
      path.set("body", t.blockStatement(body));
    } else {
      path.get("body").unshiftContainer("body", body);
    }
  }
};
Esempio n. 29
0
Ep.getUnmarkedCurrentLoc = function() {
  return t.numericLiteral(this.listing.length);
};
Esempio n. 30
-6
function extractStyles({
  src,
  styleGroups,
  namedStyleGroups,
  sourceFileName,
  whitelistedModules,
  cacheObject,
  parserPlugins,
  addCSSRequire,
  errorCallback,
  extremelyLiteMode,
}) {
  invariant(typeof src === 'string', '`src` must be a string of javascript');

  invariant(
    typeof sourceFileName === 'string' && path.isAbsolute(sourceFileName),
    '`sourceFileName` must be an absolute path to a .js file'
  );

  invariant(
    typeof cacheObject === 'object' && cacheObject !== null,
    '`cacheObject` must be an object'
  );

  if (typeof styleGroups !== 'undefined') {
    invariant(
      Array.isArray(styleGroups),
      '`styleGroups` must be an array of style prop objects'
    );
  }

  if (typeof namedStyleGroups !== 'undefined') {
    invariant(
      typeof namedStyleGroups === 'object' && namedStyleGroups !== null,
      '`namedStyleGroups` must be an object of style prop objects keyed by className'
    );
  }

  if (typeof whitelistedModules !== 'undefined') {
    invariant(
      Array.isArray(whitelistedModules),
      '`whitelistedModules` must be an array of paths to modules that are OK to require'
    );
  }

  if (typeof errorCallback !== 'undefined') {
    invariant(
      typeof errorCallback === 'function',
      '`errorCallback` is expected to be a function'
    );
  } else {
    errorCallback = console.warn;
  }

  if (typeof addCSSRequire === 'undefined') {
    addCSSRequire = true;
  }

  const sourceDir = path.dirname(sourceFileName);

  // Using a map for (officially supported) guaranteed insertion order
  const cssMap = new Map();

  const ast = parse(src, parserPlugins);

  let jsxstyleSrc;
  const validComponents = {};
  let useImportSyntax = false;
  let hasValidComponents = false;
  let needsRuntimeJsxstyle = false;

  if (typeof extremelyLiteMode === 'string') {
    jsxstyleSrc =
      extremelyLiteMode === 'react'
        ? 'jsxstyle'
        : `jsxstyle-${extremelyLiteMode}`;
    Object.assign(validComponents, liteComponents);
    hasValidComponents = true;
  }

  // Find jsxstyle require in program root
  ast.program.body = ast.program.body.filter(item => {
    if (t.isVariableDeclaration(item)) {
      for (let idx = -1, len = item.declarations.length; ++idx < len; ) {
        const dec = item.declarations[idx];

        if (
          // var ...
          !t.isVariableDeclarator(dec) ||
          // var {...}
          !t.isObjectPattern(dec.id) ||
          // var {x} = require(...)
          !t.isCallExpression(dec.init) ||
          !t.isIdentifier(dec.init.callee) ||
          dec.init.callee.name !== 'require' ||
          // var {x} = require('one-thing')
          dec.init.arguments.length !== 1 ||
          !t.isStringLiteral(dec.init.arguments[0])
        ) {
          continue;
        }

        // var {x} = require('jsxstyle')
        if (!JSXSTYLE_SOURCES.hasOwnProperty(dec.init.arguments[0].value)) {
          continue;
        }

        if (jsxstyleSrc) {
          invariant(
            jsxstyleSrc === dec.init.arguments[0].value,
            'Expected duplicate `require` to be from "%s", received "%s"',
            jsxstyleSrc,
            dec.init.arguments[0].value
          );
        }

        for (let idx = -1, len = dec.id.properties.length; ++idx < len; ) {
          const prop = dec.id.properties[idx];
          if (
            !t.isObjectProperty(prop) ||
            !t.isIdentifier(prop.key) ||
            !t.isIdentifier(prop.value)
          ) {
            continue;
          }

          // only add uppercase identifiers to validComponents
          if (
            prop.key.name[0] !== prop.key.name[0].toUpperCase() ||
            prop.value.name[0] !== prop.value.name[0].toUpperCase()
          ) {
            continue;
          }

          // map imported name to source component name
          validComponents[prop.value.name] = prop.key.name;
          hasValidComponents = true;
        }

        jsxstyleSrc = dec.init.arguments[0].value;

        // if this is the only variable declaration, remove it
        // TODO: handle weird const a = 1, b = 2; maybe
        if (len === 1) return false;
      }
    } else if (t.isImportDeclaration(item)) {
      // omfg everyone please just use import syntax

      // not imported from jsxstyle? byeeee
      if (
        !t.isStringLiteral(item.source) ||
        !JSXSTYLE_SOURCES.hasOwnProperty(item.source.value)
      ) {
        return true;
      }

      if (jsxstyleSrc) {
        invariant(
          jsxstyleSrc === item.source.value,
          'Expected duplicate `import` to be from "%s", received "%s"',
          jsxstyleSrc,
          item.source.value
        );
      }

      jsxstyleSrc = item.source.value;
      useImportSyntax = true;

      for (let idx = -1, len = item.specifiers.length; ++idx < len; ) {
        const specifier = item.specifiers[idx];
        if (
          !t.isImportSpecifier(specifier) ||
          !t.isIdentifier(specifier.imported) ||
          !t.isIdentifier(specifier.local)
        ) {
          continue;
        }

        if (
          specifier.imported.name[0] !==
            specifier.imported.name[0].toUpperCase() ||
          specifier.local.name[0] !== specifier.local.name[0].toUpperCase()
        ) {
          continue;
        }

        validComponents[specifier.local.name] = specifier.imported.name;
        hasValidComponents = true;
      }

      // remove import
      return false;
    }
    return true;
  });

  // jsxstyle isn't included anywhere, so let's bail
  if (!jsxstyleSrc || !hasValidComponents) {
    return {
      js: src,
      css: '',
      cssFileName: null,
      ast,
      map: null,
    };
  }

  // class or className?
  const classPropName =
    jsxstyleSrc === 'jsxstyle-preact' ? 'class' : 'className';

  // Generate a UID that's unique in the program scope
  let boxComponentName;
  traverse(ast, {
    Program(path) {
      boxComponentName = path.scope.generateUid('Box');
    },
  });

  traverse(ast, {
    JSXElement(path) {
      const node = path.node.openingElement;

      if (
        // skip non-identifier opening elements (member expressions, etc.)
        !t.isJSXIdentifier(node.name) ||
        // skip non-jsxstyle components
        !validComponents.hasOwnProperty(node.name.name)
      ) {
        return;
      }

      // Remember the source component
      const originalNodeName = node.name.name;
      const src = validComponents[originalNodeName];

      const removeAllTrace = liteComponents.hasOwnProperty(originalNodeName);

      if (!removeAllTrace) {
        node.name.name = boxComponentName;
      }

      const defaultProps = componentStyles[src];
      invariant(defaultProps, `jsxstyle component <${src} /> does not exist!`);

      const propKeys = Object.keys(defaultProps);
      // looping backwards because of unshift
      for (let idx = propKeys.length; --idx >= 0; ) {
        const prop = propKeys[idx];
        const value = defaultProps[prop];
        if (value == null || value === '') {
          continue;
        }

        let valueEx;
        if (typeof value === 'number') {
          valueEx = t.jSXExpressionContainer(t.numericLiteral(value));
        } else if (typeof value === 'string') {
          valueEx = t.stringLiteral(value);
        } else {
          continue;
        }

        node.attributes.unshift(t.jSXAttribute(t.jSXIdentifier(prop), valueEx));
      }

      // Generate scope object at this level
      const staticNamespace = getStaticBindingsForScope(
        path.scope,
        whitelistedModules,
        sourceFileName
      );
      const evalContext = vm.createContext(staticNamespace);

      let lastSpreadIndex = null;
      const flattenedAttributes = [];
      node.attributes.forEach(attr => {
        if (t.isJSXSpreadAttribute(attr)) {
          if (canEvaluate(staticNamespace, attr.argument)) {
            const spreadValue = vm.runInContext(
              generate(attr.argument).code,
              evalContext
            );

            if (typeof spreadValue !== 'object' || spreadValue === null) {
              lastSpreadIndex = flattenedAttributes.push(attr) - 1;
            } else {
              for (const k in spreadValue) {
                const value = spreadValue[k];

                if (typeof value === 'number') {
                  flattenedAttributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(k),
                      t.jSXExpressionContainer(t.numericLiteral(value))
                    )
                  );
                } else if (value === null) {
                  // why would you ever do this
                  flattenedAttributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(k),
                      t.jSXExpressionContainer(t.nullLiteral())
                    )
                  );
                } else {
                  // toString anything else
                  // TODO: is this a bad idea
                  flattenedAttributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(k),
                      t.jSXExpressionContainer(t.stringLiteral('' + value))
                    )
                  );
                }
              }
            }
          } else {
            lastSpreadIndex = flattenedAttributes.push(attr) - 1;
          }
        } else {
          flattenedAttributes.push(attr);
        }
      });

      node.attributes = flattenedAttributes;

      let propsAttributes;
      const staticAttributes = {};
      let inlinePropCount = 0;

      const staticTernaries = [];

      node.attributes = node.attributes.filter((attribute, idx) => {
        if (
          // keep the weirdos
          !attribute.name ||
          !attribute.name.name ||
          // haven't hit the last spread operator
          idx < lastSpreadIndex
        ) {
          inlinePropCount++;
          return true;
        }

        const name = attribute.name.name;
        const value = t.isJSXExpressionContainer(attribute.value)
          ? attribute.value.expression
          : attribute.value;

        // if one or more spread operators are present and we haven't hit the last one yet, the prop stays inline
        if (lastSpreadIndex !== null && idx <= lastSpreadIndex) {
          inlinePropCount++;
          return true;
        }

        // pass ref, key, and style props through untouched
        if (UNTOUCHED_PROPS.hasOwnProperty(name)) {
          return true;
        }

        // className prop will be handled below
        if (name === classPropName) {
          return true;
        }

        // validate component prop
        if (name === 'component') {
          if (t.isLiteral(value) && typeof value.value === 'string') {
            const char1 = value.value[0];
            // component="article"
            if (char1 === char1.toUpperCase()) {
              // an uppercase string with be turned into a component, which is not what we want.
              // TODO: look into transforming to React.createElement.
              // main downside is that further transformations that rely on JSX won't work.
              inlinePropCount++;
            }
          } else if (t.isIdentifier(value)) {
            const char1 = value.name[0];
            // component={Avatar}
            if (char1 === char1.toLowerCase()) {
              // a lowercase identifier will be transformed to a DOM element. that's not good.
              inlinePropCount++;
            }
          } else if (t.isMemberExpression(value)) {
            // component={variable.prop}
          } else {
            // TODO: extract more complex expressions out as separate var
            errorCallback(
              '`component` prop value was not handled by extractStyles: %s',
              generate(value).code
            );
            inlinePropCount++;
          }
          return true;
        }

        // pass key and style props through untouched
        if (UNTOUCHED_PROPS.hasOwnProperty(name)) {
          return true;
        }

        if (name === 'props') {
          if (!value) {
            errorCallback('`props` prop does not have a value');
            inlinePropCount++;
            return true;
          }

          if (t.isObjectExpression(value)) {
            let errorCount = 0;
            const attributes = [];

            for (const k in value.properties) {
              const propObj = value.properties[k];

              if (t.isObjectProperty(propObj)) {
                let key;

                if (t.isIdentifier(propObj.key)) {
                  key = propObj.key.name;
                } else if (t.isStringLiteral(propObj.key)) {
                  // starts with a-z or _ followed by a-z, -, or _
                  if (/^\w[\w-]+$/.test(propObj.key.value)) {
                    key = propObj.key.value;
                  } else {
                    errorCallback(
                      '`props` prop contains an invalid key: `%s`',
                      propObj.key.value
                    );
                    errorCount++;
                    continue;
                  }
                } else {
                  errorCallback(
                    'unhandled object property key type: `%s`',
                    propObj.type
                  );
                  errorCount++;
                }

                if (ALL_SPECIAL_PROPS.hasOwnProperty(key)) {
                  errorCallback(
                    '`props` prop cannot contain `%s` as it is used by jsxstyle and will be overwritten.',
                    key
                  );
                  errorCount++;
                  continue;
                }

                if (t.isStringLiteral(propObj.value)) {
                  // convert literal value back to literal to ensure it has double quotes (siiiigh)
                  attributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(key),
                      t.stringLiteral(propObj.value.value)
                    )
                  );
                } else {
                  // wrap everything else in a JSXExpressionContainer
                  attributes.push(
                    t.jSXAttribute(
                      t.jSXIdentifier(key),
                      t.jSXExpressionContainer(propObj.value)
                    )
                  );
                }
              } else if (t.isSpreadProperty(propObj)) {
                attributes.push(t.jSXSpreadAttribute(propObj.argument));
              } else {
                errorCallback(
                  'unhandled object property value type: `%s`',
                  propObj.type
                );
                errorCount++;
              }
            }

            if (errorCount > 0) {
              inlinePropCount++;
            } else {
              propsAttributes = attributes;
            }

            return true;
          }

          if (
            // if it's not an object, spread it
            // props={wow()}
            t.isCallExpression(value) ||
            // props={wow.cool}
            t.isMemberExpression(value) ||
            // props={wow}
            t.isIdentifier(value)
          ) {
            propsAttributes = [t.jSXSpreadAttribute(value)];
            return true;
          }

          // if props prop is weird-looking, leave it and warn
          errorCallback('props prop is an unhandled type: `%s`', value.type);
          inlinePropCount++;
          return true;
        }

        if (name === 'mediaQueries') {
          if (canEvaluateObject(staticNamespace, value)) {
            staticAttributes[name] = vm.runInContext(
              // parens so V8 doesn't parse the object like a block
              '(' + generate(value).code + ')',
              evalContext
            );
          } else if (canEvaluate(staticNamespace, value)) {
            staticAttributes[name] = vm.runInContext(
              generate(value).code,
              evalContext
            );
          } else {
            inlinePropCount++;
            return true;
          }
          return false;
        }

        // if value can be evaluated, extract it and filter it out
        if (canEvaluate(staticNamespace, value)) {
          staticAttributes[name] = vm.runInContext(
            generate(value).code,
            evalContext
          );
          return false;
        }

        if (t.isConditionalExpression(value)) {
          // if both sides of the ternary can be evaluated, extract them
          if (
            canEvaluate(staticNamespace, value.consequent) &&
            canEvaluate(staticNamespace, value.alternate)
          ) {
            staticTernaries.push({ name, ternary: value });
            // mark the prop as extracted
            staticAttributes[name] = null;
            return false;
          }
        } else if (t.isLogicalExpression(value)) {
          // convert a simple logical expression to a ternary with a null alternate
          if (
            value.operator === '&&' &&
            canEvaluate(staticNamespace, value.right)
          ) {
            staticTernaries.push({
              name,
              ternary: {
                test: value.left,
                consequent: value.right,
                alternate: t.nullLiteral(),
              },
            });
            staticAttributes[name] = null;
            return false;
          }
        }

        if (removeAllTrace) {
          errorCallback(
            'jsxstyle-loader encountered a dynamic prop (`%s`) on a lite ' +
              'jsxstyle component (`%s`). If you would like to pass dynamic ' +
              'styles to this component, specify them in the `style` prop.',
            generate(attribute).code,
            originalNodeName
          );
          return false;
        }

        // if we've made it this far, the prop stays inline
        inlinePropCount++;
        return true;
      });

      let classNamePropValue;
      const classNamePropIndex = node.attributes.findIndex(
        attr => attr.name && attr.name.name === classPropName
      );
      if (classNamePropIndex > -1 && Object.keys(staticAttributes).length > 0) {
        classNamePropValue = getPropValueFromAttributes(
          classPropName,
          node.attributes
        );
        node.attributes.splice(classNamePropIndex, 1);
      }

      // if all style props have been extracted, jsxstyle component can be
      // converted to a div or the specified component
      if (inlinePropCount === 0) {
        const propsPropIndex = node.attributes.findIndex(
          attr => attr.name && attr.name.name === 'props'
        );
        // deal with props prop
        if (propsPropIndex > -1) {
          if (propsAttributes) {
            propsAttributes.forEach(a => node.attributes.push(a));
          }
          // delete props prop
          node.attributes.splice(propsPropIndex, 1);
        }

        const componentPropIndex = node.attributes.findIndex(
          attr => attr.name && attr.name.name === 'component'
        );
        if (componentPropIndex > -1) {
          const attribute = node.attributes[componentPropIndex];
          const componentPropValue = t.isJSXExpressionContainer(attribute.value)
            ? attribute.value.expression
            : attribute.value;

          if (
            t.isLiteral(componentPropValue) &&
            typeof componentPropValue.value === 'string'
          ) {
            node.name.name = componentPropValue.value;
          } else if (t.isIdentifier(componentPropValue)) {
            node.name.name = componentPropValue.name;
          } else if (t.isMemberExpression(componentPropValue)) {
            node.name.name = generate(componentPropValue).code;
          }

          // remove component prop from attributes
          node.attributes.splice(componentPropIndex, 1);
        } else {
          node.name.name = 'div';
        }
      } else {
        needsRuntimeJsxstyle = true;
        if (lastSpreadIndex !== null) {
          // if only some style props were extracted AND additional props are spread onto the component,
          // add the props back with null values to prevent spread props from incorrectly overwriting the extracted prop value
          Object.keys(staticAttributes).forEach(attr => {
            node.attributes.push(
              t.jSXAttribute(
                t.jSXIdentifier(attr),
                t.jSXExpressionContainer(t.nullLiteral())
              )
            );
          });
        }
      }

      if (path.node.closingElement) {
        path.node.closingElement.name.name = node.name.name;
      }

      const stylesByClassName = getStylesByClassName(
        styleGroups,
        namedStyleGroups,
        staticAttributes,
        cacheObject
      );

      const extractedStyleClassNames = Object.keys(stylesByClassName).join(' ');

      const classNameObjects = [];

      if (classNamePropValue) {
        if (canEvaluate(null, classNamePropValue)) {
          // TODO: don't use canEvaluate here, need to be more specific
          classNameObjects.push(
            t.stringLiteral(
              vm.runInNewContext(generate(classNamePropValue).code)
            )
          );
        } else {
          classNameObjects.push(classNamePropValue);
        }
      }

      if (staticTernaries.length > 0) {
        const ternaryObj = extractStaticTernaries(
          staticTernaries,
          evalContext,
          cacheObject
        );

        // ternaryObj is null if all of the extracted ternaries have falsey consequents and alternates
        if (ternaryObj !== null) {
          // add extracted styles by className to existing object
          Object.assign(stylesByClassName, ternaryObj.stylesByClassName);
          classNameObjects.push(ternaryObj.ternaryExpression);
        }
      }

      if (extractedStyleClassNames) {
        classNameObjects.push(t.stringLiteral(extractedStyleClassNames));
      }

      const classNamePropValueForReals = classNameObjects.reduce((acc, val) => {
        if (!acc) {
          if (
            // pass conditional expressions through
            t.isConditionalExpression(val) ||
            // pass non-null literals through
            (t.isLiteral(val) && val.value !== null)
          ) {
            return val;
          }
          return t.logicalExpression('||', val, t.stringLiteral(''));
        }

        const accIsString = t.isLiteral(acc) && typeof acc.value === 'string';

        let inner;
        if (t.isLiteral(val)) {
          if (typeof val.value === 'string') {
            if (accIsString) {
              // join adjacent string literals
              return t.stringLiteral(`${acc.value} ${val.value}`);
            }
            inner = t.stringLiteral(` ${val.value}`);
          } else {
            inner = t.binaryExpression('+', t.stringLiteral(' '), val);
          }
        } else if (
          t.isConditionalExpression(val) ||
          t.isBinaryExpression(val)
        ) {
          if (accIsString) {
            return t.binaryExpression(
              '+',
              t.stringLiteral(`${acc.value} `),
              val
            );
          }
          inner = t.binaryExpression('+', t.stringLiteral(' '), val);
        } else if (t.isIdentifier(val) || t.isMemberExpression(val)) {
          // identifiers and member expressions make for reasonable ternaries
          inner = t.conditionalExpression(
            val,
            t.binaryExpression('+', t.stringLiteral(' '), val),
            t.stringLiteral('')
          );
        } else {
          if (accIsString) {
            return t.binaryExpression(
              '+',
              t.stringLiteral(`${acc.value} `),
              t.logicalExpression('||', val, t.stringLiteral(''))
            );
          }
          // use a logical expression for more complex prop values
          inner = t.binaryExpression(
            '+',
            t.stringLiteral(' '),
            t.logicalExpression('||', val, t.stringLiteral(''))
          );
        }
        return t.binaryExpression('+', acc, inner);
      }, null);

      if (classNamePropValueForReals) {
        if (
          t.isLiteral(classNamePropValueForReals) &&
          typeof classNamePropValueForReals.value === 'string'
        ) {
          node.attributes.push(
            t.jSXAttribute(
              t.jSXIdentifier(classPropName),
              t.stringLiteral(classNamePropValueForReals.value)
            )
          );
        } else {
          node.attributes.push(
            t.jSXAttribute(
              t.jSXIdentifier(classPropName),
              t.jSXExpressionContainer(classNamePropValueForReals)
            )
          );
        }
      }

      const lineNumbers =
        node.loc.start.line +
        (node.loc.start.line !== node.loc.end.line
          ? `-${node.loc.end.line}`
          : '');

      const comment = util.format(
        '/* %s:%s (%s) */',
        sourceFileName.replace(process.cwd(), '.'),
        lineNumbers,
        originalNodeName
      );

      for (const className in stylesByClassName) {
        if (cssMap.has(className)) {
          if (comment) {
            const val = cssMap.get(className);
            val.commentTexts.push(comment);
            cssMap.set(className, val);
          }
        } else {
          let css = '';
          const styleProps = stylesByClassName[className];

          // get object of style objects
          const styleObjects = getStyleKeysForProps(styleProps, true);
          delete styleObjects.classNameKey;
          const styleObjectKeys = Object.keys(styleObjects).sort();

          for (let idx = -1, len = styleObjectKeys.length; ++idx < len; ) {
            const k = styleObjectKeys[idx];
            const item = styleObjects[k];
            let itemCSS =
              `.${className}` +
              (item.pseudoclass ? ':' + item.pseudoclass : '') +
              (item.pseudoelement ? '::' + item.pseudoelement : '') +
              ` {${item.styles}}`;

            if (item.mediaQuery) {
              itemCSS = `@media ${item.mediaQuery} { ${itemCSS} }`;
            }
            css += itemCSS + '\n';
          }

          cssMap.set(className, { css, commentTexts: [comment] });
        }
      }
    },
  });

  const css = Array.from(cssMap.values())
    .map(n => n.commentTexts.map(t => `${t}\n`).join('') + n.css)
    .join('');
  // path.parse doesn't exist in the webpack'd bundle but path.dirname and path.basename do.
  const baseName = path.basename(sourceFileName, '.js');
  const cssRelativeFileName = `./${baseName}.jsxstyle.css`;
  const cssFileName = path.join(sourceDir, cssRelativeFileName);

  // Conditionally add Box import/require to the top of the document
  if (needsRuntimeJsxstyle) {
    if (useImportSyntax) {
      ast.program.body.unshift(
        t.importDeclaration(
          [
            t.importSpecifier(
              t.identifier(boxComponentName),
              t.identifier('Box')
            ),
          ],
          t.stringLiteral(jsxstyleSrc)
        )
      );
    } else {
      ast.program.body.unshift(
        t.variableDeclaration('var', [
          t.variableDeclarator(
            t.identifier(boxComponentName),
            t.memberExpression(
              t.callExpression(t.identifier('require'), [
                t.stringLiteral(jsxstyleSrc),
              ]),
              t.identifier('Box')
            )
          ),
        ])
      );
    }
  }

  // append require/import statement to the document
  if (css !== '') {
    if (useImportSyntax) {
      ast.program.body.unshift(
        t.importDeclaration([], t.stringLiteral(cssRelativeFileName))
      );
    } else {
      ast.program.body.unshift(
        t.expressionStatement(
          t.callExpression(t.identifier('require'), [
            t.stringLiteral(cssRelativeFileName),
          ])
        )
      );
    }
  }

  const result = generate(
    ast,
    {
      fileName: sourceFileName,
      retainLines: false,
      compact: 'auto',
      concise: false,
      sourceMaps: true,
      sourceFileName,
      quotes: 'single',
    },
    src
  );

  return {
    js: result.code,
    css,
    cssFileName,
    ast: result.ast,
    map: result.map,
  };
}