Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Stylable variables exports type and generated dts and sourcemaps files #2256

Merged
merged 12 commits into from
Jan 17, 2022
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);
tzachbon marked this conversation as resolved.
Show resolved Hide resolved
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(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe open an issue (or comment on the value parser issue) about this as a tech debt

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