Skip to content

Commit cde2302

Browse files
committed
feat: add @replexica/compiler utils
1 parent 387fbde commit cde2302

4 files changed

Lines changed: 192 additions & 0 deletions

File tree

packages/compiler/src/utils/ast.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import * as t from "@babel/types";
2+
import { traverse, NodePath } from "@babel/core";
3+
import { trimSafely } from "./text";
4+
5+
export function attributeExists(path: NodePath<t.JSXElement>, name: string) {
6+
const openingElement = path.get("openingElement");
7+
return openingElement.node.attributes.some((attr) => {
8+
if (!t.isJSXAttribute(attr)) { return false; }
9+
const result = t.isJSXIdentifier(attr.name) && attr.name.name === name;
10+
return result;
11+
});
12+
}
13+
14+
export function findImmediateJsxParent(path: NodePath<t.Node>): NodePath<t.JSXElement | t.JSXFragment> | null {
15+
const parentPath = path.parentPath;
16+
if (parentPath?.isJSXElement()) {
17+
return parentPath;
18+
} else if (parentPath?.isJSXFragment()) {
19+
return parentPath;
20+
} else {
21+
return null;
22+
}
23+
}
24+
25+
export function hasJsxTextChildren(jsxElement: NodePath<t.JSXElement | t.JSXFragment>): boolean {
26+
return jsxElement.node.children.some(child => t.isJSXText(child) && trimSafely(child.value).length > 0);
27+
}
28+
29+
export function getJsxParentLine(path: NodePath<t.JSXElement | t.JSXFragment>, parentLine: NodePath<t.JSXElement | t.JSXFragment>[] = [path]): NodePath<t.JSXElement | t.JSXFragment>[] {
30+
const jsxElementParent = findImmediateJsxParent(path);
31+
if (!jsxElementParent) { return parentLine; }
32+
33+
return getJsxParentLine(jsxElementParent, [...parentLine, jsxElementParent]);
34+
}
35+
36+
export function hasDirective(ast: t.File, directive: string): boolean {
37+
let found = false;
38+
traverse(ast, {
39+
Program(path) {
40+
path.node.directives.forEach((directiveNode) => {
41+
if (t.isDirective(directiveNode) && directiveNode.value.value === directive) {
42+
found = true;
43+
}
44+
});
45+
},
46+
});
47+
return found;
48+
}
49+
50+
export function getImportName(programNode: NodePath<t.Program>, libName: string, localName: string): string | null {
51+
let found = false;
52+
53+
programNode.traverse({
54+
ImportDeclaration(path) {
55+
const source = path.node.source.value;
56+
if (source !== libName) { return; }
57+
58+
const specifier = path.node.specifiers.find((specifier) =>
59+
t.isImportSpecifier(specifier) &&
60+
t.isIdentifier(specifier.local) &&
61+
specifier.local.name === localName
62+
);
63+
64+
if (specifier) {
65+
found = true;
66+
}
67+
},
68+
});
69+
70+
return found ? localName : null;
71+
}
72+
73+
export function injectImport(programNode: NodePath<t.Program>, libName: string, localName: string): string {
74+
let libImportName = `_${localName}`;
75+
76+
const helperName = getImportName(programNode, libName, libImportName);
77+
if (helperName) { return helperName; }
78+
79+
let uniqueId = 0;
80+
while (programNode.scope.hasBinding(libImportName)) {
81+
libImportName = `${libImportName}${++uniqueId}`;
82+
}
83+
84+
const importStatement = t.importDeclaration(
85+
[t.importSpecifier(t.identifier(libImportName), t.identifier(localName))],
86+
t.stringLiteral(libName),
87+
);
88+
programNode.node.body.unshift(importStatement);
89+
90+
return libImportName;
91+
}
92+
93+
export function findJsxParentForTheAttribute(path: NodePath<t.JSXAttribute>): NodePath<t.JSXElement> | null {
94+
const openingElement = path.parentPath.isJSXOpeningElement() ? path.parentPath : null;
95+
if (!openingElement) { return null; }
96+
97+
const jsxElement = openingElement.parentPath.isJSXElement() ? openingElement.parentPath : null;
98+
return jsxElement;
99+
}
100+
101+
export function parseMemberExpressionFromJsxMemberExpression(
102+
jsxMemberExpression: t.JSXMemberExpression,
103+
): t.MemberExpression {
104+
if (t.isJSXIdentifier(jsxMemberExpression.object)) {
105+
return t.memberExpression(
106+
t.identifier(jsxMemberExpression.object.name),
107+
t.identifier(jsxMemberExpression.property.name),
108+
);
109+
} else if (t.isJSXMemberExpression(jsxMemberExpression.object)) {
110+
return t.memberExpression(
111+
parseMemberExpressionFromJsxMemberExpression(jsxMemberExpression.object),
112+
t.identifier(jsxMemberExpression.property.name),
113+
);
114+
} else {
115+
throw new Error(`Unsupported JSXMemberExpression object type.`);
116+
}
117+
}
118+
119+
export function getStringAttributeValue(path: NodePath<t.JSXElement>, attributeName: string): string | null {
120+
const attribute = path.node.openingElement.attributes.find((attr) => {
121+
return t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === attributeName;
122+
}) as t.JSXAttribute | undefined;
123+
124+
if (!attribute) { return null; }
125+
if (!attribute.value) { return null; }
126+
if (!t.isStringLiteral(attribute.value)) { return null; }
127+
128+
return attribute.value.value;
129+
}
130+
131+
export function getJsxElementName(path: NodePath<t.JSXElement | t.JSXFragment>): string {
132+
if (t.isJSXFragment(path.node)) {
133+
return "Fragment";
134+
} else if (t.isJSXIdentifier(path.node.openingElement.name)) {
135+
return path.node.openingElement.name.name;
136+
} else if (t.isJSXMemberExpression(path.node.openingElement.name)) {
137+
return path.node.openingElement.name.property.name;
138+
} else {
139+
throw new Error(`Unsupported JSXElement name type: ${path.node.openingElement.name.type}`);
140+
}
141+
}
142+
143+
export function getJsxAttributeName(path: NodePath<t.JSXAttribute>): string {
144+
if (t.isJSXIdentifier(path.node.name)) {
145+
return path.node.name.name;
146+
} else {
147+
throw new Error(`Unsupported JSXAttribute name type: ${path.node.name.type}`);
148+
}
149+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as t from '@babel/types';
2+
import { parse } from '@babel/parser';
3+
import generate from '@babel/generator';
4+
5+
export function parseCodeIntoBabelAst(code: string) {
6+
return parse(code, {
7+
sourceType: 'module',
8+
plugins: ['jsx', 'typescript'],
9+
});
10+
}
11+
12+
export function generateCodeFromBabelAst(code: string, ast: t.File) {
13+
return generate(ast, {}, code);
14+
}

packages/compiler/src/utils/id.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import createObjectHash from 'object-hash';
2+
3+
export function generateChunkId(text: string, nonce: number): string {
4+
return createHash({ text, nonce });
5+
}
6+
7+
export function generateScopeId(chunksIds: string[], nonce: number): string {
8+
return createHash({ chunksIds, nonce });
9+
}
10+
11+
export function generateFileId(relativeFilePath: string, nonce: number): string {
12+
// to make sure id remains the same for both windows and unix
13+
// we need to split by both / and \
14+
// and then join by /
15+
const normalizedPath = relativeFilePath.split(/[\\/]/).join('/');
16+
return createHash({ normalizedPath, nonce });
17+
}
18+
19+
//
20+
21+
function createHash(obj: any) {
22+
const length = 12;
23+
const hash = createObjectHash(obj, { algorithm: 'md5' });
24+
const shortHash = hash.substring(0, length);
25+
return shortHash;
26+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function trimSafely(text: string) {
2+
return text.trim();
3+
}

0 commit comments

Comments
 (0)