Skip to content

Commit

Permalink
fix: Stylable variables exports type and generated dts and `sourcem…
Browse files Browse the repository at this point in the history
…aps` files (#2256)
  • Loading branch information
tzachbon committed Jan 17, 2022
1 parent 25e3cfe commit e27149b
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 38 deletions.
18 changes: 11 additions & 7 deletions packages/core/src/process-declaration-functions.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import type { Declaration } from 'postcss';
import { AnyValueNode, parseValues, stringifyValues } from 'css-selector-tokenizer';

type OnFunction = (node: AnyValueNode, level: number) => void;

export function processDeclarationFunctions(
decl: Declaration,
onFunction: (node: AnyValueNode) => void,
onFunction: OnFunction,
transform = false
) {
const ast = parseValues(decl.value);

ast.nodes.forEach((node) => findFunction(node, onFunction));
ast.nodes.forEach((node) => findFunction(node, onFunction, 1));

if (transform) {
decl.value = stringifyValues(ast);
}
}

function findFunction(node: AnyValueNode, onFunctionNode: (node: AnyValueNode) => void) {
function findFunction(node: AnyValueNode, onFunctionNode: OnFunction, level: number) {
switch (node.type) {
case 'value':
case 'values':
node.nodes.forEach((child) => findFunction(child, onFunctionNode));
onFunctionNode(node, level);
node.nodes.forEach((child) => findFunction(child, onFunctionNode, level));
break;
case 'url':
onFunctionNode(node);
case 'item':
onFunctionNode(node, level);
break;
case 'nested-item':
onFunctionNode(node);
node.nodes.forEach((child) => findFunction(child, onFunctionNode));
onFunctionNode(node, level);
node.nodes.forEach((child) => findFunction(child, onFunctionNode, level + 1));
break;
}
}
6 changes: 4 additions & 2 deletions packages/core/src/stylable-transformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ export interface KeyFrameWithNode {
node: postcss.Node;
}

type StVar = string | { [key: string]: StVar } | StVar[];

export interface StylableExports {
classes: Record<string, string>;
vars: Record<string, string>;
stVars: Record<string, string>;
stVars: Record<string, StVar>;
keyframes: Record<string, string>;
}

Expand Down Expand Up @@ -263,7 +265,7 @@ export class StylableTransformer {
}
public exportLocalVars(
meta: StylableMeta,
stVarsExport: Record<string, string>,
stVarsExport: StylableExports['stVars'],
variableOverride?: Record<string, string>
) {
for (const varSymbol of meta.vars) {
Expand Down
51 changes: 42 additions & 9 deletions packages/module-utils/src/dts-rough-tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ export function getLocalClassStates(local: string, tokens: TokenizedDtsEntry[])
throw new Error(`Could not find states for class ${local}`);
}

const parenthesesClosures = {
'}': '{',
']': '[',
} as const;

const isDelimiter = (char: string) =>
char === ':' ||
char === ';' ||
Expand Down Expand Up @@ -221,34 +226,62 @@ function findDtsTokens(tokens: DTSCodeToken[]) {
s.peek().value === 'const' &&
isRelevantKey(s.peek(2).value)
) {
const levels: { [key in '{' | '[']?: number } = {};
const values = new WeakSet<DTSCodeToken>();
const start = t.start;
s.next(); // const
const declareType = s.next(); // name
s.next(); // ;

const resTokens: DtsToken[] = []; // {...resTokens[]}
while ((t = s.next())) {
if (!t.type || t.type === '}') {
if (values.has(t)) {
// registered as value of token
continue;
}
if (!t.type) {
break;
}
if (t.type === '{' || t.type === '[') {
if (!levels[t.type]) {
levels[t.type] = 0;
}

levels[t.type]!++;
}

if (t.type === '}' || t.type === ']') {
levels[parenthesesClosures[t.type]]!--;

if (Object.values(levels).every((level) => level <= 0)) {
break;
}
}

if (t.type === '\n') {
lastNewLinePosition.line += 1;
lastNewLinePosition.columm = t.end;
} else if (t.type === 'string') {
s.next(); // :
const value = s.next(); // value
s.next(); // ;
resTokens.push({
}
if (t.type === 'string') {
const token: DtsToken = {
...t,
line: lastNewLinePosition.line,
column: t.start - lastNewLinePosition.columm,
outputValue: {
};

// in case this token has a string value token we add it to current token object
const value = s.peek(2);
if (value.type === 'string' || value.type === 'text') {
values.add(value);

token.outputValue = {
...value,
line: lastNewLinePosition.line,
column: value.start - lastNewLinePosition.columm,
},
});
};
}

resTokens.push(token);
}
}

Expand Down
87 changes: 73 additions & 14 deletions packages/module-utils/src/generate-dts-sourcemaps.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { basename } from 'path';
import { ClassSymbol, StylableMeta, valueMapping } from '@stylable/core';
import { CSSKeyframes } from '@stylable/core/dist/features';
import { processDeclarationFunctions } from '@stylable/core/dist/process-declaration-functions';
import { encode } from 'vlq';
import {
ClassesToken,
ClassStateToken,
TokenizedDtsEntry,
tokenizeDTS,
} from './dts-rough-tokenizer';
import { SPACING } from './generate-dts';

type LineMapping = Array<Array<number>>;

Expand Down Expand Up @@ -50,25 +52,57 @@ function getVarsSrcPosition(varName: string, meta: StylableMeta): Position | und

function getStVarsSrcPosition(varName: string, meta: StylableMeta): Position | undefined {
const stVar = meta.vars.find((v) => v.name === varName);
let res;

if (stVar) {
if (stVar?.node.source?.start) {
return {
line: stVar.node.source.start.line - 1,
column: stVar.node.source.start.column - 1,
};
} else {
// TODO: move this logic to Stylable core and enhance it. The meta should provide the API to get to the inner parts of the st-var
let res: Position;
meta.rawAst.walkRules(':vars', (rule) => {
return rule.walkDecls(varName, (decl) => {
if (decl.source && decl.source.start) {
res = {
line: decl.source.start.line - 1,
column: decl.source.start.column - 1,
};
return false;
return rule.walkDecls((decl) => {
if (decl.source?.start) {
if (decl.prop === varName) {
res = {
line: decl.source.start.line - 1,
column: decl.source.start.column - 1,
};
} else {
processDeclarationFunctions(decl, (node, level) => {
if (node.type === 'item' && node.name === varName) {
const rawDeclaration = `${decl.raws.before ?? ''}${decl.prop}${
decl.raws.between ?? ''
}${decl.value}`;
const rootPosition = {
line: rule.source!.start!.line - 1,
column: rule.source!.start!.column - 1,
};

res = {
...calculateEstimatedPosition(
rawDeclaration,
node.name,
node.after,
rootPosition
),
generatedOffsetLevel: level,
};
}
});
}

if (res) {
return false;
}
}

return;
});
});
return res!;
}

return res;
}

function getKeyframeSrcPosition(keyframeName: string, meta: StylableMeta): Position | undefined {
Expand Down Expand Up @@ -107,6 +141,7 @@ function createLineMapping(dtsOffset: number, srcLine: number, srcCol: number):
type Position = {
line: number;
column: number;
generatedOffsetLevel?: number;
};

function findDefiningClassName(stateToken: ClassStateToken, entryClassName: ClassSymbol) {
Expand Down Expand Up @@ -236,11 +271,18 @@ export function generateDTSSourceMap(dtsContent: string, meta: StylableMeta) {
}

if (currentSrcPosition) {
const lineDelta = currentSrcPosition.line - lastSrcPosition.line;
const columnDelta = currentSrcPosition.column - lastSrcPosition.column;

mapping[dtsLine] = createLineMapping(
4, // top-level object property offset
currentSrcPosition.line - lastSrcPosition.line,
currentSrcPosition.column - lastSrcPosition.column
SPACING.repeat(currentSrcPosition.generatedOffsetLevel ?? 1).length,
lineDelta,
columnDelta
);

// reset to default offset level
currentSrcPosition.generatedOffsetLevel = undefined;

lastSrcPosition = { ...currentSrcPosition };
}
} else if (resToken.type === 'states') {
Expand Down Expand Up @@ -280,3 +322,20 @@ export function generateDTSSourceMap(dtsContent: string, meta: StylableMeta) {
4
);
}

function calculateEstimatedPosition(
rawValue: string,
name: string,
after = '',
rootPosition?: Position
): Position {
const valueLength = rawValue.indexOf(name + after) + name.length - after.length;
const value = rawValue.slice(0, valueLength);
const byLines = value.split(/\n/g);
const lastLine = byLines[byLines.length - 1];

return {
line: byLines.length - 1 + (rootPosition?.line ?? 0),
column: lastLine.length + (rootPosition?.column ?? 0),
};
}
46 changes: 41 additions & 5 deletions packages/module-utils/src/generate-dts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
valueMapping,
} from '@stylable/core';

const SPACING = ' '.repeat(4);
export const SPACING = ' '.repeat(4);
const asString = (v: string) => JSON.stringify(v);

function addStatesEntries(
Expand Down Expand Up @@ -63,10 +63,46 @@ function stringifyStates(meta: StylableMeta) {
return out;
}

function stringifyStringRecord(record: Record<string, string>, indent = SPACING) {
return Object.keys(record)
.map((k) => `${indent}${asString(k)}: string;`)
.join('\n');
function stringifyStringRecord(
record: Record<string, any>,
addParentheses = false,
indent = SPACING,
delimiter = '\n'
): string {
const s = Object.entries(record)
.map(
([key, value]) =>
`${indent}${asString(key)}: ${stringifyTypedValue(
value,
indent + SPACING,
delimiter
)};`
)
.join(delimiter);

return addParentheses ? `{${wrapNL(s)}${indent.replace(SPACING, '')}}` : s;
}

function stringifyStringArray(array: any[], indent = SPACING, delimiter = '\n') {
return `[${wrapNL(
array
.map((value) => `${indent}${stringifyTypedValue(value, indent + SPACING, delimiter)},`)
.join(delimiter)
)}${indent.replace(SPACING, '')}]`;
}

function stringifyTypedValue(
value: string | any[] | Record<string, any>,
indent = SPACING,
delimiter = '\n'
): string {
if (typeof value === 'string') {
return 'string';
} else if (Array.isArray(value)) {
return stringifyStringArray(value, indent, delimiter);
} else {
return stringifyStringRecord(value, true, indent, delimiter);
}
}

function stringifyClasses(classes: Record<string, string>, namespace: string, indent = SPACING) {
Expand Down
Loading

0 comments on commit e27149b

Please sign in to comment.