Skip to content

Commit

Permalink
feat: improve namespace readability and potential optimization confli…
Browse files Browse the repository at this point in the history
…ct (#2928)

* fix: allow dash in namespace
* feat:  reduce multiple dashed and underscores into a single lodash or underscore
* feat: support non-ascii namespace from filename
* feat: validate st-namespace similarly to filename ns-resolve
  • Loading branch information
idoros authored Dec 3, 2023
1 parent e69610e commit 09fb4b3
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 25 deletions.
10 changes: 5 additions & 5 deletions packages/core/src/features/st-namespace.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path';
import { createFeature, FeatureContext } from './feature';
import { plugableRecord } from '../helpers/plugable-record';
import { filename2varname } from '../helpers/string';
import { filename2varname, string2varname } from '../helpers/string';
import { stripQuotation } from '../helpers/string';
import valueParser from 'postcss-value-parser';
import { murmurhash3_32_gc } from '../murmurhash';
Expand Down Expand Up @@ -166,10 +166,10 @@ export function parseNamespace(node: AtRule, diag?: Diagnostics): string | undef
});
return;
}
// ident like - without escapes
// eslint-disable-next-line no-control-regex
if (!namespace.match(/^([a-zA-Z-_]|[^\x00-\x7F]+)([a-zA-Z-_0-9]|[^\x00-\x7F])*$/)) {
// empty namespace found
// check namespace is a valid ident start with no conflicts with stylable namespacing
const transformedNamespace = string2varname(namespace);
if (namespace !== transformedNamespace) {
// invalid namespace found
diag?.report(diagnostics.INVALID_NAMESPACE_VALUE(), {
node,
word: namespace,
Expand Down
25 changes: 22 additions & 3 deletions packages/core/src/helpers/string.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
/* eslint-disable no-control-regex */
export function stripQuotation(str: string) {
return str.replace(/^['"](.*?)['"]$/g, '$1');
}

export function filename2varname(filename: string) {
return string2varname(filename.replace(/(?=.*)\.\w+$/, '').replace(/\.st$/, ''));
return string2varname(
filename
// remove extension (eg. .css)
.replace(/(?=.*)\.\w+$/, '')
// remove potential .st extension prefix
.replace(/\.st$/, '')
);
}

function string2varname(str: string) {
return str.replace(/[^0-9a-zA-Z_]/gm, '').replace(/^[^a-zA-Z_]+/gm, '');
export function string2varname(str: string) {
return (
str
// allow only letters, numbers, dashes, underscores, and non-ascii
.replace(/[\x00-\x7F]+/gm, (matchAscii) => {
return matchAscii.replace(/[^0-9a-zA-Z_-]/gm, '');
})
// replace multiple dashes with single dash
.replace(/--+/gm, '-')
// replace multiple underscores with single underscore
.replace(/__+/gm, '_')
// remove leading digits from start
.replace(/^\d+/gm, '')
);
}
44 changes: 38 additions & 6 deletions packages/core/test/features/st-namespace.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,34 @@ describe('features/st-namespace', () => {
it('should use filename as default namespace', () => {
const { sheets } = testStylableCore({
'/a.st.css': ``,
'/b.st.css': ``,
'/B.st.css': ``,
'/-dash.st.css': ``,
'/_underscore.st.css': ``,
'/--multi---dash.st.css': ``,
'/__multi___underscore.st.css': ``,
'/🤡emoji🤷‍♀️why.st.css': ``,
'/123numbers-not-at-start789.st.css': ``,
});

const AMeta = sheets['/a.st.css'].meta;
const BMeta = sheets['/b.st.css'].meta;
const BMeta = sheets['/B.st.css'].meta;
const dashMeta = sheets['/-dash.st.css'].meta;
const underscoreMeta = sheets['/_underscore.st.css'].meta;
const multiDashMeta = sheets['/--multi---dash.st.css'].meta;
const multiUnderscoreMeta = sheets['/__multi___underscore.st.css'].meta;
const emojiMeta = sheets['/🤡emoji🤷‍♀️why.st.css'].meta;
const numbersMeta = sheets['/123numbers-not-at-start789.st.css'].meta;

expect(AMeta.namespace, 'a meta.namespace').to.eql('a');
expect(BMeta.namespace, 'b meta.namespace').to.eql('b');
expect(BMeta.namespace, 'b meta.namespace').to.eql('B');
expect(dashMeta.namespace, '-dash meta.namespace').to.eql('-dash');
expect(underscoreMeta.namespace, '_underscore meta.namespace').to.eql('_underscore');
expect(multiDashMeta.namespace, '-multi-dash meta.namespace').to.eql('-multi-dash');
expect(multiUnderscoreMeta.namespace, '_multi_underscore meta.namespace').to.eql(
'_multi_underscore'
);
expect(emojiMeta.namespace, 'emoji meta.namespace').to.eql('🤡emoji🤷‍♀️why');
expect(numbersMeta.namespace, 'numbers meta.namespace').to.eql('numbers-not-at-start789');
});
it('should override default namespace with @st-namespace', () => {
const { sheets } = testStylableCore({
Expand Down Expand Up @@ -134,9 +154,9 @@ describe('features/st-namespace', () => {
.x {}
`,
'/dash.st.css': `
@st-namespace "---";
@st-namespace "-";
/* x-@rule .\\---__x */
/* @rule .-__x */
.x {}
`,
});
Expand All @@ -155,7 +175,7 @@ describe('features/st-namespace', () => {
expect(underscoreMeta.namespace, 'underscore namespace').to.eql('_x123_');

shouldReportNoDiagnostics(dashMeta);
expect(dashMeta.namespace, 'dash namespace').to.eql('---');
expect(dashMeta.namespace, 'dash namespace').to.eql('-');
});
it('should report non string namespace', () => {
const { sheets } = testStylableCore({
Expand Down Expand Up @@ -225,6 +245,18 @@ describe('features/st-namespace', () => {
@analyze-error(start with number) word(5abc) ${diagnostics.INVALID_NAMESPACE_VALUE()}
*/
@st-namespace "5abc";
/*
@transform-remove
@analyze-error(dash sequence) word(a--b) ${diagnostics.INVALID_NAMESPACE_VALUE()}
*/
@st-namespace "a--b";
/*
@transform-remove
@analyze-error(underscore sequence) word(a__b) ${diagnostics.INVALID_NAMESPACE_VALUE()}
*/
@st-namespace "a__b";
`,
});

Expand Down
10 changes: 6 additions & 4 deletions packages/esbuild/test/e2e/simple-case/simple-case.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ const stylesInOrder = [
{
path: 'side-effects.st.css',
fileName: 'side-effects.st.css',
namespace: 'sideeffects',
namespace: 'side-effects',
},
{
path: `internal-dir${sep}internal-dir.st.css`,
fileName: 'internal-dir.st.css',
namespace: 'internaldir',
namespace: 'internal-dir',
},
{
path: 'a.st.css',
Expand Down Expand Up @@ -115,9 +115,11 @@ async function contract(
).getPropertyValue('--unused-deep'),
};
});

const assetLoaded = Boolean(responses?.find((r) => r.url().match(/asset-.*?\.png$/)));
const internalDirAsset = Boolean(responses?.find((r) => r.url().match(/internal-dir-.*?\.png$/)));
const internalDirAsset = Boolean(
responses?.find((r) => r.url().match(/internal-dir-.*?\.png$/))
);

expect(internalDirAsset, 'asset loaded from internal dir').to.eql(true);
expect(assetLoaded, 'asset loaded').to.eql(true);
Expand Down
2 changes: 1 addition & 1 deletion packages/node/test/require-hook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('require hook', () => {
attachHook({ ignoreJSModules: true });
const m = require(join(fixturesPath, 'has-js.st.css'));
expect(m.test).equal(undefined);
expect(m.namespace).to.match(/^hasjs/);
expect(m.namespace).to.match(/^has-js/);
});

it('should throw on missing config file', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/rollup-plugin/test/rollup-side-effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('StylableRollupPlugin - include all stylesheets with side-effects', fun
@layer globalLayer;
html {
--globalselector-x: green;
--global-selector-x: green;
}
.index__root {}
`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ describe(`(${project})`, () => {

expect(metadata.namespaceMapping[`/${index.hash}.st.css`]).to.match(/index\d+/);
expect(metadata.namespaceMapping[`/${comp.hash}.st.css`]).to.match(/comp\d+/);
expect(metadata.namespaceMapping[`/${compX.hash}.st.css`]).to.match(/compx\d+/);
expect(metadata.namespaceMapping[`/${compX.hash}.st.css`]).to.match(/comp-x\d+/);
});
});
8 changes: 4 additions & 4 deletions packages/webpack-plugin/test/e2e/dual-mode-esm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ describe(`(${project})`, () => {
expect(vanillaStylesNoRuntime).to.eql(stylableStylesNoRuntime);

expect(normalizeNamespace(vanillaStyles)).to.eql([
{ id: 'designsystem', depth: '1' },
{ id: 'design-system', depth: '1' },
{ id: 'label', depth: '1' },
{ id: 'button', depth: '2' },
{ id: 'basictheme', depth: '3' },
{ id: 'labeltheme', depth: '4' },
{ id: 'buttontheme', depth: '4' },
{ id: 'basic-theme', depth: '3' },
{ id: 'label-theme', depth: '4' },
{ id: 'button-theme', depth: '4' },
]);
});
});
Expand Down

0 comments on commit 09fb4b3

Please sign in to comment.