summaryrefslogtreecommitdiff
path: root/@linaria/packages/shaker/src/identifierHandlers.ts
diff options
context:
space:
mode:
Diffstat (limited to '@linaria/packages/shaker/src/identifierHandlers.ts')
-rw-r--r--@linaria/packages/shaker/src/identifierHandlers.ts130
1 files changed, 130 insertions, 0 deletions
diff --git a/@linaria/packages/shaker/src/identifierHandlers.ts b/@linaria/packages/shaker/src/identifierHandlers.ts
new file mode 100644
index 0000000..bd3c919
--- /dev/null
+++ b/@linaria/packages/shaker/src/identifierHandlers.ts
@@ -0,0 +1,130 @@
+import { types as t } from '@babel/core';
+import type { Aliases, Identifier, Node, VisitorKeys } from '@babel/types';
+import { peek } from '@linaria/babel-preset';
+import GraphBuilderState from './GraphBuilderState';
+import type { IdentifierHandlerType, NodeType } from './types';
+import { identifierHandlers as core } from './langs/core';
+import ScopeManager from './scope';
+
+type HandlerFn = <TParent extends Node = Node>(
+ builder: GraphBuilderState,
+ node: Identifier,
+ parent: TParent,
+ parentKey: VisitorKeys[TParent['type']],
+ listIdx: number | null
+) => void;
+
+type Handler = IdentifierHandlerType | HandlerFn;
+
+const handlers: {
+ [key: string]: Handler;
+} = {};
+
+function isAlias(type: NodeType): type is keyof Aliases {
+ return type in t.FLIPPED_ALIAS_KEYS;
+}
+
+export function defineHandler(
+ typeOrAlias: NodeType,
+ field: string,
+ handler: Handler
+) {
+ const types = isAlias(typeOrAlias)
+ ? t.FLIPPED_ALIAS_KEYS[typeOrAlias]
+ : [typeOrAlias];
+ types.forEach((type: string) => {
+ handlers[`${type}:${field}`] = handler;
+ });
+}
+
+export function batchDefineHandlers(
+ typesAndFields: [NodeType, ...string[]][],
+ handler: IdentifierHandlerType
+) {
+ typesAndFields.forEach(([type, ...fields]) =>
+ fields.forEach((field) => defineHandler(type, field, handler))
+ );
+}
+
+batchDefineHandlers([...core.declare], 'declare');
+
+batchDefineHandlers([...core.keep], 'keep');
+
+batchDefineHandlers([...core.refer], 'refer');
+
+/*
+ * Special case for FunctionDeclaration
+ * Function id should be defined in the parent scope
+ */
+defineHandler(
+ 'FunctionDeclaration',
+ 'id',
+ (builder: GraphBuilderState, node: Identifier) => {
+ builder.scope.declare(node, false, null, 1);
+ }
+);
+
+/*
+ * Special handler for [obj.member = 42] = [1] in different contexts
+ */
+const memberExpressionObjectHandler = (
+ builder: GraphBuilderState,
+ node: Identifier
+) => {
+ const context = peek(builder.context);
+ const declaration = builder.scope.addReference(node);
+ if (declaration) {
+ builder.graph.addEdge(node, declaration);
+
+ if (context === 'lval') {
+ // One exception here: we shake exports,
+ // so `exports` does not depend on its members' assignments.
+ if (
+ declaration !== ScopeManager.globalExportsIdentifier &&
+ declaration !== ScopeManager.globalModuleIdentifier
+ ) {
+ builder.graph.addEdge(declaration, node);
+ }
+ }
+ }
+};
+
+defineHandler('MemberExpression', 'object', memberExpressionObjectHandler);
+defineHandler(
+ 'OptionalMemberExpression',
+ 'object',
+ memberExpressionObjectHandler
+);
+
+/*
+ * Special handler for obj.member and obj[member]
+ */
+const memberExpressionPropertyHandler = (
+ builder: GraphBuilderState,
+ node: Identifier,
+ parent: Node
+) => {
+ if (t.isMemberExpression(parent) && parent.computed) {
+ const declaration = builder.scope.addReference(node);
+ // Let's check that it's not a global variable
+ if (declaration) {
+ // usage of a variable depends on its declaration
+ builder.graph.addEdge(node, declaration);
+
+ const context = peek(builder.context);
+ if (context === 'lval') {
+ // This is an identifier in the left side of an assignment expression and a variable value depends on that.
+ builder.graph.addEdge(declaration, node);
+ }
+ }
+ }
+};
+
+defineHandler('MemberExpression', 'property', memberExpressionPropertyHandler);
+defineHandler(
+ 'OptionalMemberExpression',
+ 'property',
+ memberExpressionPropertyHandler
+);
+
+export default handlers;