@workday/canvas-kit-styling-transform 10.0.0-alpha.553-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +52 -0
- package/dist/commonjs/index.d.ts +3 -0
- package/dist/commonjs/index.d.ts.map +1 -0
- package/dist/commonjs/index.js +7 -0
- package/dist/commonjs/lib/getCssVariables.d.ts +5 -0
- package/dist/commonjs/lib/getCssVariables.d.ts.map +1 -0
- package/dist/commonjs/lib/getCssVariables.js +51 -0
- package/dist/commonjs/lib/styleTransform.d.ts +12 -0
- package/dist/commonjs/lib/styleTransform.d.ts.map +1 -0
- package/dist/commonjs/lib/styleTransform.js +511 -0
- package/dist/es6/index.d.ts +3 -0
- package/dist/es6/index.d.ts.map +1 -0
- package/dist/es6/index.js +2 -0
- package/dist/es6/lib/getCssVariables.d.ts +5 -0
- package/dist/es6/lib/getCssVariables.d.ts.map +1 -0
- package/dist/es6/lib/getCssVariables.js +45 -0
- package/dist/es6/lib/styleTransform.d.ts +12 -0
- package/dist/es6/lib/styleTransform.d.ts.map +1 -0
- package/dist/es6/lib/styleTransform.js +503 -0
- package/index.ts +3 -0
- package/package.json +50 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import ts from 'typescript';
|
|
3
|
+
import { serializeStyles } from '@emotion/serialize';
|
|
4
|
+
import { base, brand } from '@workday/canvas-tokens-web';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { slugify, generateUniqueId } from '@workday/canvas-kit-styling';
|
|
7
|
+
import { getFallbackVariable, getVariablesFromFiles } from './getCssVariables';
|
|
8
|
+
const styleExpressionName = 'createStyles';
|
|
9
|
+
const cssVarExpressionName = 'cssVar';
|
|
10
|
+
const createVarExpressionName = 'createVars';
|
|
11
|
+
const styleImportString = '@workday/canvas-kit-styling';
|
|
12
|
+
const vars = {};
|
|
13
|
+
function getStyleValueFromType(node, type, checker) {
|
|
14
|
+
const value = getCSSValueAtLocation(node, checker, type);
|
|
15
|
+
if (value) {
|
|
16
|
+
if (value.startsWith('--')) {
|
|
17
|
+
return `var(${value})`;
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
const typeValue = checker.typeToString(type);
|
|
22
|
+
throw new Error(`Unknown type at: "${node.getText()}". Received "${typeValue}"\n${getErrorMessage(node)}\nFor static analysis of styles, please make sure all types resolve to string or numeric literals. Please use 'const' instead of 'let'. If using an object, cast using "as const" or use an interface with string or numeric literals.`);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A `PropertyExpression` is an expression with a dot in it. Like `a.b.c`. It may be nested. This
|
|
26
|
+
* function will walk the AST and create a string like `a.b.c` to be passed on to variable name
|
|
27
|
+
* generation. This will be used for CSS variable lookups.
|
|
28
|
+
*/
|
|
29
|
+
function getPropertyAccessExpressionText(node) {
|
|
30
|
+
if (ts.isIdentifier(node.name)) {
|
|
31
|
+
if (ts.isIdentifier(node.expression)) {
|
|
32
|
+
return `${node.expression.text}.${node.name.text}`;
|
|
33
|
+
}
|
|
34
|
+
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
35
|
+
return `${getPropertyAccessExpressionText(node.expression)}.${node.name.text}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
function parseStyleObjValue(initializer, variables, checker) {
|
|
41
|
+
/**
|
|
42
|
+
* String literals like 'red' or empty Template Expressions like `red`
|
|
43
|
+
*/
|
|
44
|
+
if (ts.isStringLiteral(initializer) || ts.isNoSubstitutionTemplateLiteral(initializer)) {
|
|
45
|
+
return initializer.text;
|
|
46
|
+
}
|
|
47
|
+
// numeric literal values like `12`
|
|
48
|
+
if (ts.isNumericLiteral(initializer)) {
|
|
49
|
+
return `${initializer.text}px`;
|
|
50
|
+
}
|
|
51
|
+
// The source file is using an identifier which will be known at runtime, we'll try to
|
|
52
|
+
// determine the type
|
|
53
|
+
if (ts.isIdentifier(initializer)) {
|
|
54
|
+
const type = checker.getTypeAtLocation(initializer);
|
|
55
|
+
return getStyleValueFromType(initializer, type, checker);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* ```ts
|
|
59
|
+
* PropertyAccessExpressions are dot-notation
|
|
60
|
+
*
|
|
61
|
+
* foo.bar.baz
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
if (ts.isPropertyAccessExpression(initializer)) {
|
|
65
|
+
const type = checker.getTypeAtLocation(initializer);
|
|
66
|
+
return getStyleValueFromType(initializer, type, checker);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* This will find patterns like:
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* cssVar(myVars.color);
|
|
73
|
+
* cssVar(myVars.colors.background);
|
|
74
|
+
* cssVar(myVars.colors.background, 'red')
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
if (ts.isCallExpression(initializer) &&
|
|
78
|
+
ts.isIdentifier(initializer.expression) &&
|
|
79
|
+
initializer.expression.text === cssVarExpressionName) {
|
|
80
|
+
const value = getCSSValueAtLocation(initializer.arguments[0], checker);
|
|
81
|
+
const value2 = initializer.arguments[1]
|
|
82
|
+
? parseStyleObjValue(initializer.arguments[1], variables, checker)
|
|
83
|
+
: undefined;
|
|
84
|
+
// handle fallback variables
|
|
85
|
+
const fallbackValue = getFallbackVariable(value, variables);
|
|
86
|
+
if (value && (value2 || fallbackValue)) {
|
|
87
|
+
return `var(${value}, ${((value2 === null || value2 === void 0 ? void 0 : value2.startsWith('--')) ? `var(${value2})` : value2) || fallbackValue})`;
|
|
88
|
+
}
|
|
89
|
+
if (value) {
|
|
90
|
+
return `var(${value})`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* ```ts
|
|
95
|
+
* `border 1px ${myVars.colors.border}`
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
if (ts.isTemplateExpression(initializer)) {
|
|
99
|
+
return getStyleValueFromTemplateExpression(initializer, variables, checker);
|
|
100
|
+
}
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Gets a static string value from a template expression. It could recurse.
|
|
105
|
+
*/
|
|
106
|
+
function getStyleValueFromTemplateExpression(node, variables, checker) {
|
|
107
|
+
if (!node) {
|
|
108
|
+
return '';
|
|
109
|
+
}
|
|
110
|
+
if (ts.isTemplateExpression(node)) {
|
|
111
|
+
return (getStyleValueFromTemplateExpression(node.head, variables, checker) +
|
|
112
|
+
node.templateSpans
|
|
113
|
+
.map(value => getStyleValueFromTemplateExpression(value, variables, checker))
|
|
114
|
+
.join(''));
|
|
115
|
+
}
|
|
116
|
+
if (ts.isTemplateHead(node) || ts.isTemplateTail(node) || ts.isTemplateMiddle(node)) {
|
|
117
|
+
return node.text;
|
|
118
|
+
}
|
|
119
|
+
if (ts.isTemplateSpan(node)) {
|
|
120
|
+
return (parseStyleObjValue(node.expression, variables, checker) +
|
|
121
|
+
getStyleValueFromTemplateExpression(node.literal, variables, checker));
|
|
122
|
+
}
|
|
123
|
+
return '';
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Gets a CSS value from an AST node
|
|
127
|
+
*/
|
|
128
|
+
function getCSSValueAtLocation(node, checker,
|
|
129
|
+
/**
|
|
130
|
+
* Optional type. This works for cases where the node is a TypeNode or TypeScript infers the Type
|
|
131
|
+
* via a generic resolution. For example:
|
|
132
|
+
* ```ts
|
|
133
|
+
* function someFn<T extends string>(input: T): {fontSize: T} {
|
|
134
|
+
* return { fontSize: input }
|
|
135
|
+
* }
|
|
136
|
+
*
|
|
137
|
+
* // in styles
|
|
138
|
+
* ...someFn('12px')
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* If we don't pass a type of the property given by `type.getProperties()`, TypeScript will
|
|
142
|
+
* resolve the type at the value node as `T` instead of `12px`. Allowing for a type override is
|
|
143
|
+
* useful when the caller may have more context about the type at a given node than we do.
|
|
144
|
+
*/
|
|
145
|
+
type = checker.getTypeAtLocation(node)) {
|
|
146
|
+
const varsKey = getVarsKeyFromNode(node);
|
|
147
|
+
if (vars[varsKey]) {
|
|
148
|
+
return vars[varsKey];
|
|
149
|
+
}
|
|
150
|
+
if (type.isStringLiteral()) {
|
|
151
|
+
// This isn't a component variable, it is a static CSS variable
|
|
152
|
+
return type.value;
|
|
153
|
+
}
|
|
154
|
+
if (type.isNumberLiteral()) {
|
|
155
|
+
return `${type.value}px`;
|
|
156
|
+
}
|
|
157
|
+
if (node && ts.isPropertyAccessExpression(node)) {
|
|
158
|
+
return getPropertyAccessExpressionText(node);
|
|
159
|
+
}
|
|
160
|
+
return '';
|
|
161
|
+
}
|
|
162
|
+
function getModuleSpecifierFromDeclaration(node) {
|
|
163
|
+
if (!node) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
if (ts.isImportSpecifier(node) && ts.isStringLiteral(node.parent.parent.parent.moduleSpecifier)) {
|
|
167
|
+
return node.parent.parent.parent.moduleSpecifier.text;
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
function getStyleFromProperty(property, prefix, variables, checker) {
|
|
172
|
+
if (ts.isPropertyAssignment(property)) {
|
|
173
|
+
// All properties should be non-objects
|
|
174
|
+
// {foo: 'bar'}
|
|
175
|
+
if (ts.isIdentifier(property.name)) {
|
|
176
|
+
const value = parseStyleObjValue(property.initializer, variables, checker);
|
|
177
|
+
if (value) {
|
|
178
|
+
return { [property.name.text]: value };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (ts.isComputedPropertyName(property.name)) {
|
|
182
|
+
if (ts.isPropertyAccessExpression(property.name.expression)) {
|
|
183
|
+
const value = parseStyleObjValue(property.initializer, variables, checker);
|
|
184
|
+
if (value) {
|
|
185
|
+
// test if the property is a static value
|
|
186
|
+
getPropertyAccessExpressionText(property.name.expression);
|
|
187
|
+
const type = checker.getTypeAtLocation(property.name.expression);
|
|
188
|
+
checker.typeToString(type);
|
|
189
|
+
if (type.isStringLiteral()) {
|
|
190
|
+
return { [type.value]: value };
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const expressionText = getPropertyAccessExpressionText(property.name.expression);
|
|
194
|
+
const [id, name] = getVariableNameParts(expressionText);
|
|
195
|
+
return { [`--${prefix}-${slugify(id)}-${name}`]: value };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// String literal property names are special selectors with more styles
|
|
201
|
+
// {'&:hover': {}}
|
|
202
|
+
if (ts.isStringLiteral(property.name)) {
|
|
203
|
+
return {
|
|
204
|
+
[property.name.text]: parseStyleObjFromNode(property.initializer, prefix, variables, checker),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* A spread assignment looks like:
|
|
210
|
+
*
|
|
211
|
+
* ```ts
|
|
212
|
+
* {
|
|
213
|
+
* ...styles
|
|
214
|
+
* }
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* https://ts-ast-viewer.com/#code/MYewdgzgLgBFCmBbADjAvDA3gMxCAXDAOQBGAhgE5EC+AUKJLAigEzpYB0Xzy1QA
|
|
218
|
+
*/
|
|
219
|
+
if (ts.isSpreadAssignment(property)) {
|
|
220
|
+
// Detect `focusRing` calls. This is temporary until we figure out a better way to do focus
|
|
221
|
+
// rings that doesn't require a special entry in the transform function.
|
|
222
|
+
//
|
|
223
|
+
// TODO: implement a fully working type resolver for CSS variables or remove support for them an
|
|
224
|
+
// remove all uses of `focusRing` from new styling code
|
|
225
|
+
if (ts.isCallExpression(property.expression) &&
|
|
226
|
+
ts.isIdentifier(property.expression.expression) &&
|
|
227
|
+
property.expression.expression.text === 'focusRing') {
|
|
228
|
+
const argumentObject = property.expression.arguments[0];
|
|
229
|
+
// defaults
|
|
230
|
+
const defaults = {
|
|
231
|
+
width: '2px',
|
|
232
|
+
separation: '0px',
|
|
233
|
+
inset: undefined,
|
|
234
|
+
innerColor: `var(${base.frenchVanilla100}, rgba(255,255,255,1))`,
|
|
235
|
+
outerColor: `var(${brand.common.focusOutline}, rgba(8,117,225,1))`,
|
|
236
|
+
};
|
|
237
|
+
if (argumentObject && ts.isObjectLiteralExpression(argumentObject)) {
|
|
238
|
+
argumentObject.properties.forEach(property => {
|
|
239
|
+
if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name)) {
|
|
240
|
+
defaults[property.name.text] = parseStyleObjValue(property.initializer, variables, checker);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
let boxShadow;
|
|
244
|
+
switch (defaults.inset) {
|
|
245
|
+
case 'outer':
|
|
246
|
+
boxShadow = `inset 0 0 0 ${defaults.separation} ${defaults.outerColor}, inset 0 0 0 calc(${defaults.width} + ${defaults.separation}) ${defaults.innerColor}`;
|
|
247
|
+
break;
|
|
248
|
+
case 'inner':
|
|
249
|
+
boxShadow = `inset 0 0 0 ${defaults.separation} ${defaults.innerColor}, 0 0 0 ${defaults.width} ${defaults.outerColor}`;
|
|
250
|
+
break;
|
|
251
|
+
default:
|
|
252
|
+
boxShadow = `0 0 0 ${defaults.separation} ${defaults.innerColor}, 0 0 0 calc(${defaults.width} + ${defaults.separation}) ${defaults.outerColor}`;
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
return { boxShadow };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Spread assignments are a bit complicated to use the AST to figure out, so we'll ask the
|
|
259
|
+
// TypeScript type checker.
|
|
260
|
+
const type = checker.getTypeAtLocation(property.expression);
|
|
261
|
+
checker.typeToString(type); //?
|
|
262
|
+
return parseStyleObjFromType(type, prefix, variables, checker);
|
|
263
|
+
}
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* If we're here, we have a `ts.Type` that represents a style object. We try to parse a style object
|
|
268
|
+
* from the AST, but we might have something that is more complicated like a function call or an
|
|
269
|
+
* identifier that represents an object. It could be imported from another file.
|
|
270
|
+
*/
|
|
271
|
+
function parseStyleObjFromType(type, prefix, variables, checker) {
|
|
272
|
+
const styleObj = {};
|
|
273
|
+
// Gets all the properties of the type object
|
|
274
|
+
return type.getProperties().reduce((result, property) => {
|
|
275
|
+
const declaration = property.declarations[0];
|
|
276
|
+
if (declaration) {
|
|
277
|
+
const propType = checker.getTypeOfSymbolAtLocation(property, declaration);
|
|
278
|
+
return {
|
|
279
|
+
...result,
|
|
280
|
+
[property.name]: getStyleValueFromType(declaration, propType, checker),
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}, styleObj);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* If the node is an `ObjectLiteralExpression`, we'll walk the `properties` of the AST node and
|
|
288
|
+
* create a style object for each property we find.
|
|
289
|
+
*/
|
|
290
|
+
function parseStyleObjFromNode(node, prefix, variables, checker) {
|
|
291
|
+
const styleObj = {};
|
|
292
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
293
|
+
return node.properties.reduce((result, property) => {
|
|
294
|
+
return { ...result, ...getStyleFromProperty(property, prefix, variables, checker) };
|
|
295
|
+
}, styleObj);
|
|
296
|
+
}
|
|
297
|
+
return styleObj;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Creates an AST node representation of the passed in `styleObj`, but in the format of `{name:
|
|
301
|
+
* string, styles: serializedStyles}`. The `name` is hard-coded here to work with both server-side
|
|
302
|
+
* and client-side style injection. This results in a stable style key for Emotion while also
|
|
303
|
+
* optimizing style serialization.
|
|
304
|
+
*/
|
|
305
|
+
function createStyleObjectNode(styleObj) {
|
|
306
|
+
const serialized = serializeStyles([styleObj]);
|
|
307
|
+
const styleText = serialized.styles;
|
|
308
|
+
const styleExpression = ts.factory.createStringLiteral(styleText);
|
|
309
|
+
// create an emotion-optimized object: https://github.com/emotion-js/emotion/blob/f3b268f7c52103979402da919c9c0dd3f9e0e189/packages/serialize/src/index.js#L315-L322
|
|
310
|
+
// Looks like: `{name: $hash, styles: $styleText }`
|
|
311
|
+
return ts.factory.createObjectLiteralExpression([
|
|
312
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier('name'),
|
|
313
|
+
// TODO - we may need this to be a static variable for the CSS package
|
|
314
|
+
ts.factory.createStringLiteral(generateUniqueId()) // We might be using values that are resolved at runtime, but should still be static. We're only supporting the `cs` function running once per file, so a stable id based on a hash is not necessary
|
|
315
|
+
),
|
|
316
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier('styles'), styleExpression // In the future we can extract CSS from here by running the `stylis` compiler directly. Emotion does this here: https://github.com/emotion-js/emotion/blob/f3b268f7c52103979402da919c9c0dd3f9e0e189/packages/cache/src/index.js#L188-L245
|
|
317
|
+
),
|
|
318
|
+
], false);
|
|
319
|
+
}
|
|
320
|
+
export default function styleTransformer(program, { prefix = 'css', variables = {}, fallbackFiles } = {
|
|
321
|
+
prefix: 'css',
|
|
322
|
+
variables: {},
|
|
323
|
+
}) {
|
|
324
|
+
if (fallbackFiles) {
|
|
325
|
+
const files = fallbackFiles
|
|
326
|
+
.filter(file => file) // don't process empty files
|
|
327
|
+
.map(file => {
|
|
328
|
+
// Find the fully-qualified path name. This could error which should give "module not found" errors
|
|
329
|
+
return file.startsWith('.') ? path.resolve(process.cwd(), file) : require.resolve(file);
|
|
330
|
+
})
|
|
331
|
+
.map(file => ts.sys.readFile(file) || '');
|
|
332
|
+
// eslint-disable-next-line no-param-reassign
|
|
333
|
+
variables = getVariablesFromFiles(files);
|
|
334
|
+
}
|
|
335
|
+
const checker = program.getTypeChecker();
|
|
336
|
+
return context => {
|
|
337
|
+
const visit = node => {
|
|
338
|
+
// eslint-disable-next-line no-param-reassign
|
|
339
|
+
node = ts.visitEachChild(node, visit, context);
|
|
340
|
+
/**
|
|
341
|
+
* Check if the node is a call expression that looks like:
|
|
342
|
+
*
|
|
343
|
+
* ```ts
|
|
344
|
+
* createStyles({
|
|
345
|
+
* // properties
|
|
346
|
+
* })
|
|
347
|
+
* ```
|
|
348
|
+
*
|
|
349
|
+
* It will also make sure the `createStyles` function was imported from
|
|
350
|
+
* `@workday/canvas-kit-styling` to ensure we don't rewrite the AST of code we don't own.
|
|
351
|
+
*
|
|
352
|
+
* This transformation will pre-serialize the style objects and turn them into strings for
|
|
353
|
+
* faster runtime processing in Emotion. The following is an example of the transformation.
|
|
354
|
+
*
|
|
355
|
+
* ```ts
|
|
356
|
+
* // before transformation
|
|
357
|
+
* const myStyles = createStyles({
|
|
358
|
+
* fontSize: '1rem'
|
|
359
|
+
* })
|
|
360
|
+
*
|
|
361
|
+
* // after transformation
|
|
362
|
+
* const myStyles = createStyles({
|
|
363
|
+
* name: 'abc123',
|
|
364
|
+
* styles: 'font-size: 1rem;'
|
|
365
|
+
* })
|
|
366
|
+
* ```
|
|
367
|
+
*
|
|
368
|
+
* The after transformation already serialized the styles and goes through a shortcut process
|
|
369
|
+
* in `@emotion/css` where only the Emotion cache is checked and styles are inserted if the
|
|
370
|
+
* cache key wasn't found.
|
|
371
|
+
*/
|
|
372
|
+
if (ts.isCallExpression(node) &&
|
|
373
|
+
ts.isIdentifier(node.expression) &&
|
|
374
|
+
node.expression.text === styleExpressionName &&
|
|
375
|
+
node.arguments.length > 0) {
|
|
376
|
+
// get the declaration of the symbol of the styleExpression
|
|
377
|
+
const symbol = checker.getSymbolAtLocation(node.expression);
|
|
378
|
+
const declaration = symbol === null || symbol === void 0 ? void 0 : symbol.declarations[0];
|
|
379
|
+
if (getModuleSpecifierFromDeclaration(declaration) === styleImportString) {
|
|
380
|
+
const newArguments = [...node.arguments].map(arg => {
|
|
381
|
+
// An `ObjectLiteralExpression` is an object like `{foo:'bar'}`:
|
|
382
|
+
// https://ts-ast-viewer.com/#code/MYewdgzgLgBFCmBbADjAvDA3gKBjAZiCAFwwDkARgIYBOZ2AvkA
|
|
383
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
384
|
+
const styleObj = parseStyleObjFromNode(arg, prefix, variables, checker);
|
|
385
|
+
return createStyleObjectNode(styleObj);
|
|
386
|
+
}
|
|
387
|
+
// An Identifier is a variable. It could come from anywhere - imports, earlier
|
|
388
|
+
// assignments, etc. The easiest thing to do is to ask the TypeScript type checker what
|
|
389
|
+
// the type representation is and go from there.
|
|
390
|
+
if (ts.isIdentifier(arg)) {
|
|
391
|
+
const type = checker.getTypeAtLocation(arg);
|
|
392
|
+
// `createStyles` accepts strings as class names. If the class name is
|
|
393
|
+
if (type.isStringLiteral() || type.getFlags() & ts.TypeFlags.String) {
|
|
394
|
+
return arg;
|
|
395
|
+
}
|
|
396
|
+
// The type must be a object
|
|
397
|
+
const styleObj = parseStyleObjFromType(type, prefix, variables, checker);
|
|
398
|
+
return createStyleObjectNode(styleObj);
|
|
399
|
+
}
|
|
400
|
+
return arg;
|
|
401
|
+
});
|
|
402
|
+
return ts.factory.createCallExpression(ts.factory.createIdentifier(styleExpressionName), [], newArguments);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* This will create a variable
|
|
407
|
+
*/
|
|
408
|
+
if (ts.isCallExpression(node) &&
|
|
409
|
+
ts.isIdentifier(node.expression) &&
|
|
410
|
+
node.expression.text === createVarExpressionName) {
|
|
411
|
+
const id = slugify(getVarName(node));
|
|
412
|
+
const variables = node.arguments
|
|
413
|
+
.map(arg => ts.isStringLiteral(arg) && arg.text)
|
|
414
|
+
.filter(Boolean);
|
|
415
|
+
variables.forEach(v => {
|
|
416
|
+
vars[`${id}-${v}`] = `--${prefix}-${id}-${v}`;
|
|
417
|
+
});
|
|
418
|
+
return ts.factory.createCallExpression(node.expression, [], [
|
|
419
|
+
ts.factory.createObjectLiteralExpression([
|
|
420
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier('id'), ts.factory.createStringLiteral(`${prefix}-${id}`)),
|
|
421
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier('args'), ts.factory.createArrayLiteralExpression(variables.map(val => ts.factory.createStringLiteral(val)), false)),
|
|
422
|
+
], false),
|
|
423
|
+
]);
|
|
424
|
+
}
|
|
425
|
+
return node;
|
|
426
|
+
};
|
|
427
|
+
return node => ts.visitNode(node, visit);
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
// This should only be used for tests
|
|
431
|
+
export function transform(program, fileName, options) {
|
|
432
|
+
const source = program.getSourceFile(fileName) || ts.createSourceFile(fileName, '', ts.ScriptTarget.ES2019);
|
|
433
|
+
const printer = ts.createPrinter();
|
|
434
|
+
return printer.printFile(ts
|
|
435
|
+
.transform(source, [styleTransformer(program, options)])
|
|
436
|
+
.transformed.find(s => (s.fileName = fileName)) || source);
|
|
437
|
+
}
|
|
438
|
+
function getVarName(node) {
|
|
439
|
+
const parent = node.parent;
|
|
440
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
441
|
+
return parent.name.text;
|
|
442
|
+
}
|
|
443
|
+
if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
|
|
444
|
+
return `${getVarName(parent.parent)}-${parent.name.text}`;
|
|
445
|
+
}
|
|
446
|
+
return '';
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Creates an error message around a node. It will look something like:
|
|
450
|
+
*
|
|
451
|
+
* ```
|
|
452
|
+
* Unknown type at: "fontSize".
|
|
453
|
+
* File: test.ts, Line: 6, Character: 17.
|
|
454
|
+
* const styles = createStyles({
|
|
455
|
+
* fontSize: fontSize
|
|
456
|
+
* ========
|
|
457
|
+
* })
|
|
458
|
+
* ```
|
|
459
|
+
*/
|
|
460
|
+
function getErrorMessage(node) {
|
|
461
|
+
const sourceFile = node.getSourceFile();
|
|
462
|
+
const { line } = node.getSourceFile().getLineAndCharacterOfPosition(node.pos);
|
|
463
|
+
const lineStarts = sourceFile.getLineStarts();
|
|
464
|
+
const lineStartIndex = lineStarts.findIndex(s => s >= node.pos) - 1;
|
|
465
|
+
// get a whole line's text given a lineStarts index
|
|
466
|
+
function getLine(sourceFile, startIndex) {
|
|
467
|
+
const lineStarts = sourceFile.getLineStarts();
|
|
468
|
+
return sourceFile.text.substring(lineStarts[Math.max(0, startIndex)], startIndex + 1 >= lineStarts.length ? undefined : lineStarts[startIndex + 1]);
|
|
469
|
+
}
|
|
470
|
+
// Create a full context message with source code and highlighting
|
|
471
|
+
const lineBefore = getLine(sourceFile, lineStartIndex - 1);
|
|
472
|
+
const lineCurrent = getLine(sourceFile, lineStartIndex);
|
|
473
|
+
const lineAfter = getLine(sourceFile, lineStartIndex + 1);
|
|
474
|
+
const highlightedLine = ''
|
|
475
|
+
.padStart(node.getStart() - lineStarts[lineStartIndex], ' ')
|
|
476
|
+
.padEnd(node.getStart() - lineStarts[lineStartIndex] + node.getWidth(), '=') + '\n';
|
|
477
|
+
/** This should look something like:
|
|
478
|
+
* ```
|
|
479
|
+
* const styles = createStyles({
|
|
480
|
+
* fontSize: fontSize
|
|
481
|
+
* ========
|
|
482
|
+
* })
|
|
483
|
+
* ```
|
|
484
|
+
*/
|
|
485
|
+
const fullContext = lineBefore + lineCurrent + highlightedLine + lineAfter;
|
|
486
|
+
const character = node.getStart() - lineStarts[lineStartIndex] + 1;
|
|
487
|
+
return `File: ${sourceFile.fileName}:${line + 1}:${character}.\n${fullContext}`;
|
|
488
|
+
}
|
|
489
|
+
function getVariableNameParts(input) {
|
|
490
|
+
const parts = input.split('.');
|
|
491
|
+
// grab the last item in the array. This will also mutate the array, removing the last item
|
|
492
|
+
const variable = parts.pop();
|
|
493
|
+
return [parts.join('.'), variable];
|
|
494
|
+
}
|
|
495
|
+
function getVarsKeyFromNode(node) {
|
|
496
|
+
if (ts.isIdentifier(node)) {
|
|
497
|
+
return slugify(node.text);
|
|
498
|
+
}
|
|
499
|
+
if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.name)) {
|
|
500
|
+
return `${getVarsKeyFromNode(node.expression)}-${node.name.text}`;
|
|
501
|
+
}
|
|
502
|
+
return '';
|
|
503
|
+
}
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@workday/canvas-kit-styling-transform",
|
|
3
|
+
"version": "10.0.0-alpha.553-next.0",
|
|
4
|
+
"description": "The custom CSS in JS solution that takes JS styles and turns them into static CSS",
|
|
5
|
+
"author": "Workday, Inc. (https://www.workday.com)",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"main": "dist/commonjs/index.js",
|
|
8
|
+
"module": "dist/es6/index.js",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"types": "dist/es6/index.d.ts",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/workday/canvas-kit.git",
|
|
14
|
+
"directory": "modules/styling/parser"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"*/package.json",
|
|
18
|
+
"*/lib/*",
|
|
19
|
+
"*/index.ts",
|
|
20
|
+
"dist/",
|
|
21
|
+
"index.ts"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"watch": "yarn build:es6 -w",
|
|
25
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
26
|
+
"clean": "rimraf dist && rimraf .build-info && mkdirp dist",
|
|
27
|
+
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
28
|
+
"build:es6": "tsc -p tsconfig.es6.json",
|
|
29
|
+
"build:rebuild": "npm-run-all clean build",
|
|
30
|
+
"build": "npm-run-all --parallel build:cjs build:es6",
|
|
31
|
+
"depcheck": "node ../../utils/check-dependencies-exist.js",
|
|
32
|
+
"typecheck:src": "tsc -p . --noEmit --incremental false"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"canvas",
|
|
36
|
+
"canvas-kit",
|
|
37
|
+
"react",
|
|
38
|
+
"components",
|
|
39
|
+
"workday",
|
|
40
|
+
"styling"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@emotion/serialize": "^1.0.2",
|
|
44
|
+
"@workday/canvas-kit-styling": "^10.0.0-alpha.553-next.0",
|
|
45
|
+
"@workday/canvas-tokens-web": "0.1.6",
|
|
46
|
+
"stylis": "4.0.13",
|
|
47
|
+
"typescript": "4.2"
|
|
48
|
+
},
|
|
49
|
+
"gitHead": "3b30b5d170238acce838e1b74da0a65e82810de3"
|
|
50
|
+
}
|