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

feat(core): expose css custom properties api #2943

Merged
merged 4 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions packages/core/src/features/css-custom-property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@ import {
generateScopedCSSVar,
atPropertyValidationWarnings,
} from '../helpers/css-custom-property';
import type { Stylable } from '../stylable';
import { validateAllowedNodesUntil, stringifyFunction } from '../helpers/value';
import { globalValue, GLOBAL_FUNC } from '../helpers/global';
import { plugableRecord } from '../helpers/plugable-record';
import { createDiagnosticReporter } from '../diagnostics';
import { createDiagnosticReporter, Diagnostics } from '../diagnostics';
import type { StylableMeta } from '../stylable-meta';
import type { StylableResolver, CSSResolve } from '../stylable-resolver';
import {
type StylableResolver,
type CSSResolve,
createSymbolResolverWithCache,
} from '../stylable-resolver';
import type * as postcss from 'postcss';
// ToDo: refactor out - parse once and pass to hooks
import postcssValueParser from 'postcss-value-parser';
Expand Down Expand Up @@ -283,6 +288,54 @@ function addCSSProperty({
});
}

const UNKNOWN_LOCATION = {
idoros marked this conversation as resolved.
Show resolved Hide resolved
offset: -1,
line: -1,
column: -1,
} as const;

export class StylablePublicApi {
constructor(private stylable: Stylable) {}

public getProperties(meta: StylableMeta) {
const results: Record<
string,
{
meta: StylableMeta;
localName: string;
targetName: string;
source: {
meta: StylableMeta;
start: postcss.Position;
end: postcss.Position;
};
}
> = {};

const topLevelDiagnostics = new Diagnostics();
const getResolvedSymbols = createSymbolResolverWithCache(
this.stylable.resolver,
topLevelDiagnostics
);
const { cssVar } = getResolvedSymbols(meta);
for (const [name, symbol] of Object.entries(cssVar)) {
const defAst = STSymbol.getSymbolAstNode(symbol.meta, symbol.symbol);
results[name] = {
meta: symbol.meta,
localName: symbol.symbol.name,
targetName: getTransformedName(symbol),
source: {
meta: symbol.meta,
start: defAst?.source?.start || UNKNOWN_LOCATION,
end: defAst?.source?.end || UNKNOWN_LOCATION,
},
};
}

return results;
}
}

function analyzeDeclValueVarCalls(context: FeatureContext, decl: postcss.Declaration) {
const parsed = postcssValueParser(decl.value);
parsed.walk((node) => {
Expand Down
16 changes: 15 additions & 1 deletion packages/core/src/features/st-symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ function createState(clone?: State): State {
container: clone ? { ...clone.byType.container } : {},
var: clone ? { ...clone.byType.var } : {},
},
symbolToAst: new WeakMap(),
};
}

Expand Down Expand Up @@ -113,6 +114,7 @@ interface State {
byType: {
[T in keyof SymbolMap]: Record<string, SymbolMap[T]>;
};
symbolToAst: WeakMap<StylableSymbol, postcss.Node>;
}

const dataKey = plugableRecord.key<State>('mappedSymbols');
Expand Down Expand Up @@ -173,7 +175,10 @@ export function addSymbol({
safeRedeclare?: boolean;
localName?: string;
}) {
const { byNS, byNSFlat, byType } = plugableRecord.getUnsafe(context.meta.data, dataKey);
const { byNS, byNSFlat, byType, symbolToAst } = plugableRecord.getUnsafe(
context.meta.data,
dataKey
);
const name = localName || symbol.name;
const typeTable = byType[symbol._kind];
const nsName = NAMESPACES[symbol._kind];
Expand All @@ -187,9 +192,18 @@ export function addSymbol({
byNS[nsName].push({ name, symbol, ast: node, safeRedeclare });
byNSFlat[nsName][name] = symbol;
typeTable[name] = symbol;
node && symbolToAst.set(symbol, node);
return symbol;
}

export function getSymbolAstNode(
meta: StylableMeta,
symbol: StylableSymbol
): postcss.Node | undefined {
const { symbolToAst } = plugableRecord.getUnsafe(meta.data, dataKey);
return symbolToAst.get(symbol);
}

export function reportRedeclare(context: FeatureContext) {
const { byNS } = plugableRecord.getUnsafe(context.meta.data, dataKey);
for (const symbols of Object.values(byNS)) {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/stylable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class Stylable {
public resolver: StylableResolver;
public stModule = new STImport.StylablePublicApi(this);
public stScope = new STScope.StylablePublicApi(this);
public cssCustomProperty = new CSSCustomProperty.StylablePublicApi(this);
public stVar = new STVar.StylablePublicApi(this);
public stMixin = new STMixin.StylablePublicApi(this);
public cssClass = new CSSClass.StylablePublicApi(this);
Expand Down
129 changes: 128 additions & 1 deletion packages/core/test/features/css-custom-property.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
diagnosticBankReportToStrings,
deindent,
} from '@stylable/core-test-kit';
import { expect } from 'chai';
import chai, { expect } from 'chai';
import chaiSubset from 'chai-subset';
import type { StylableMeta } from '../../src';

chai.use(chaiSubset);

const stImportDiagnostics = diagnosticBankReportToStrings(STImport.diagnostics);
const stSymbolDiagnostics = diagnosticBankReportToStrings(STSymbol.diagnostics);
Expand Down Expand Up @@ -1105,4 +1109,127 @@ describe(`features/css-custom-property`, () => {
);
});
});
describe('introspection', () => {
function expectSourceLocation({
source: { meta, start, end },
expected,
}: {
source: { meta: StylableMeta; start: { offset: number }; end: { offset: number } };
expected: string;
}) {
const actualSrc = meta.sourceAst.toString().slice(start.offset, end.offset);
expect(actualSrc).to.eql(expected);
}
describe('getProperties', () => {
it('should resolve all local properties', () => {
const { stylable, sheets } = testStylableCore(
deindent(`
@property --defInAtRule {
syntax: '<color>';
initial-value: green;
inherits: false;
}

.root {
--defineInPropName: green;

color: var(--defineInDeclValue);
}
`)
);

const { meta } = sheets['/entry.st.css'];

const properties = stylable.cssCustomProperty.getProperties(meta);

expect(properties).to.containSubset({
'--defInAtRule': {
meta,
localName: '--defInAtRule',
targetName: '--entry-defInAtRule',
},
'--defineInPropName': {
meta,
localName: '--defineInPropName',
targetName: '--entry-defineInPropName',
},
'--defineInDeclValue': {
meta,
localName: '--defineInDeclValue',
targetName: '--entry-defineInDeclValue',
},
});
expectSourceLocation({
source: properties['--defInAtRule'].source,
expected: `@property --defInAtRule {\n syntax: '<color>';\n initial-value: green;\n inherits: false;\n}`,
});
expectSourceLocation({
source: properties['--defineInPropName'].source,
expected: `--defineInPropName: green;`,
});
expectSourceLocation({
source: properties['--defineInDeclValue'].source,
expected: `color: var(--defineInDeclValue);`,
});
});
it('should resolve imported properties', () => {
const { stylable, sheets } = testStylableCore({
'deep.st.css': `
.x {
--deep: red;
}
`,
'proxy.st.css': `
@st-import [--deep as --deepReassign1] from './deep.st.css';
.x {
--proxy: var(--deepReassign1);
}
`,
'entry.st.css': deindent(`
@st-import [--proxy as --proxyReassign, --deepReassign1 as --deepReassign2] from './proxy.st.css';

.x {
--local: green;
}
`),
});

const { meta } = sheets['/entry.st.css'];
const { meta: proxyMeta } = sheets['/proxy.st.css'];
const { meta: deepMeta } = sheets['/deep.st.css'];

const properties = stylable.cssCustomProperty.getProperties(meta);

expect(properties).to.containSubset({
'--local': {
meta,
localName: '--local',
targetName: '--entry-local',
},
'--proxyReassign': {
meta: proxyMeta,
localName: '--proxy',
targetName: '--proxy-proxy',
},
'--deepReassign2': {
meta: deepMeta,
localName: '--deep',
targetName: '--deep-deep',
},
});
expectSourceLocation({
source: properties['--local'].source,
expected: `--local: green;`,
});
expectSourceLocation({
source: properties['--proxyReassign'].source,
expected: `--proxy: var(--deepReassign1);`,
});
expectSourceLocation({
source: properties['--deepReassign2'].source,
expected: `--deep: red;`,
});
});
});
});
});
Loading