Skip to content

Commit a1f1d3f

Browse files
committed
Reorganize transformer into a factory
- factory accepts options - add index that exports the factory as default - update test runner
1 parent 7c1ea0a commit a1f1d3f

File tree

6 files changed

+141
-115
lines changed

6 files changed

+141
-115
lines changed

src/__tests__/baselines.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import 'jest';
22
import * as ts from 'typescript';
33
import * as fs from 'fs';
4-
import transformer from '../transformer';
4+
import createTransformer from '../';
55

66
const printer = ts.createPrinter();
7+
const transformer = createTransformer();
78

89
interface TransformBaseline {
910
type: 'transform-baseline';
@@ -32,6 +33,7 @@ ${indent(obj.transformed)}
3233
`
3334
});
3435

36+
3537
function expectTransform(filename: string) {
3638
const content = fs.readFileSync(__dirname + '/baselines/' + filename).toString();
3739
const sourceFile = ts.createSourceFile(filename, content, ts.ScriptTarget.Latest);

src/createTransformer.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import * as ts from 'typescript';
2+
3+
import {Options} from './models/Options';
4+
5+
/** Detects that a node represents a styled function
6+
* Recognizes the following patterns:
7+
*
8+
* styled.tag
9+
* Component.extend
10+
* styled(Component)
11+
* styledFunction.attrs(attributes)
12+
*/
13+
function isStyledFunction(node: ts.Node): boolean {
14+
if (node.kind === ts.SyntaxKind.PropertyAccessExpression) {
15+
if (isStyledObject((node as ts.PropertyAccessExpression).expression)) {
16+
return true;
17+
}
18+
19+
if ((node as ts.PropertyAccessExpression).name.text === 'extend'
20+
&& isValidComponent((node as ts.PropertyAccessExpression).expression)) {
21+
22+
return true;
23+
}
24+
25+
return false;
26+
}
27+
28+
if (node.kind === ts.SyntaxKind.CallExpression
29+
&& (node as ts.CallExpression).arguments.length === 1) {
30+
31+
if (isStyledObject((node as ts.CallExpression).expression)) {
32+
return true;
33+
}
34+
35+
if (isStyledAttrs((node as ts.CallExpression).expression)) {
36+
return true;
37+
}
38+
}
39+
40+
return false;
41+
}
42+
43+
function isStyledObject(node: ts.Node) {
44+
return node && node.kind === ts.SyntaxKind.Identifier && (node as ts.Identifier).text === 'styled';
45+
}
46+
47+
function isValidComponent(node: ts.Node) {
48+
return node && node.kind === ts.SyntaxKind.Identifier && isValidComponentName((node as ts.Identifier).text);
49+
}
50+
51+
function isValidTagName(name: string) {
52+
return name[0] === name[0].toLowerCase();
53+
}
54+
55+
function isValidComponentName(name: string) {
56+
return name[0] === name[0].toUpperCase();
57+
}
58+
59+
function isStyledAttrs(node: ts.Node) {
60+
return node && node.kind === ts.SyntaxKind.PropertyAccessExpression
61+
&& (node as ts.PropertyAccessExpression).name.text === 'attrs'
62+
&& isStyledFunction((node as ts.PropertyAccessExpression).expression);
63+
}
64+
65+
function defaultGetDisplayName(filename: string, bindingName: string | undefined): string | undefined {
66+
return bindingName;
67+
}
68+
69+
export function createTransformer({ getDisplayName = defaultGetDisplayName }: Partial<Options> = {}) {
70+
/**
71+
* Infers display name of a styled component.
72+
* Recognizes the following patterns:
73+
*
74+
* (const|var|let) ComponentName = styled...
75+
* export default styled...
76+
*/
77+
function getDisplayNameFromNode(node: ts.Node): string | undefined {
78+
if (node.kind === ts.SyntaxKind.VariableDeclaration
79+
&& (node as ts.VariableDeclaration).name.kind === ts.SyntaxKind.Identifier) {
80+
return getDisplayName(node.getSourceFile().fileName, ((node as ts.VariableDeclaration).name as ts.Identifier).text);
81+
}
82+
83+
if (node.kind === ts.SyntaxKind.ExportAssignment) {
84+
return getDisplayName(node.getSourceFile().fileName, undefined);
85+
}
86+
87+
return undefined;
88+
}
89+
90+
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
91+
const visitor: ts.Visitor = (node) => {
92+
if (node.parent
93+
&& node.parent.kind === ts.SyntaxKind.TaggedTemplateExpression
94+
&& (node.parent as ts.TaggedTemplateExpression).tag === node
95+
&& node.parent.parent
96+
&& node.parent.parent.kind === ts.SyntaxKind.VariableDeclaration
97+
&& isStyledFunction(node)) {
98+
99+
const displayName = getDisplayNameFromNode(node.parent.parent);
100+
101+
if (displayName) {
102+
return ts.createCall(
103+
ts.createPropertyAccess(node as ts.Expression, 'withConfig'),
104+
undefined,
105+
[ts.createObjectLiteral([ts.createPropertyAssignment('displayName', ts.createLiteral(displayName))])]);
106+
}
107+
}
108+
109+
ts.forEachChild(node, n => {
110+
if (!n.parent)
111+
n.parent = node;
112+
});
113+
114+
return ts.visitEachChild(node, visitor, context);
115+
}
116+
117+
return (node) => ts.visitNode(node, visitor);
118+
};
119+
120+
return transformer;
121+
}
122+
123+
export default createTransformer;

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import createTransformer from './createTransformer';
2+
3+
export { createTransformer };
4+
export default createTransformer;

src/models/Options.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface Options {
2+
/**
3+
* This method is used to determine component display name from filename and its binding name.
4+
*
5+
* Default strategy is to use bindingName if it's defined and use inference algorithm from filename otherwise.
6+
*/
7+
getDisplayName(filename: string, bindingName: string | undefined): string | undefined;
8+
}

src/runner.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as ts from 'typescript';
22

3-
import sampleTransformer from './transformer';
3+
import createTransformer from './';
4+
5+
const sampleTransformer = createTransformer();
46

57
function main(files: string[]) {
68
// Normally these would be parsed from tsconfig.json

src/transformer.ts

Lines changed: 0 additions & 113 deletions
This file was deleted.

0 commit comments

Comments
 (0)