Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const invalidConstsFilenameWithoutRestrictedExports = (suggestedExtension) =>
`Only variables from \`stylex.defineConsts()\` can be exported from a file with a \`${suggestedExtension}\` extension.`;
const invalidExportFromConstsFiles =
'Files that export variables from `stylex.defineConsts()` must not export anything else.';
const aliasedExport = (exportedName, localName) =>
`\`${exportedName}\` is another name for \`${localName}\`. StyleX derives variable names from the export name, so importing \`${exportedName}\` references variables that are never defined. Export each \`stylex.defineVars()\`, \`stylex.defineConsts()\`, or \`stylex.defineMarker()\` result under exactly one name.`;
const invalidDefaultExport = (type) =>
`Default exports are not allowed for variables from \`stylex.${type}()\`. Use named exports instead.`;

Expand Down Expand Up @@ -1022,6 +1024,106 @@ ruleTester.run('stylex-enforce-extension', rule.default, {
options: [{ enforceDefineConstsExtension: true }],
errors: [{ message: invalidExportFromConstsFiles }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const vars = stylex.defineVars({ color: 'red' });
const helper = someFunction();
export { helper };
`,
filename: 'myComponent.stylex.jsx',
errors: [{ message: invalidExportFromThemeFiles }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const vars = stylex.defineVars({ color: 'red' });
export { helper } from './helper';
`,
filename: 'myComponent.stylex.jsx',
errors: [{ message: invalidExportFromThemeFiles }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const consts = stylex.defineConsts({ color: 'red' });
const helper = someFunction();
export { helper };
`,
filename: 'myComponent.stylex.const.jsx',
options: [{ enforceDefineConstsExtension: true }],
errors: [{ message: invalidExportFromConstsFiles }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const vars = stylex.defineVars({ color: 'red' });
export * from './helpers';
`,
filename: 'myComponent.stylex.jsx',
errors: [{ message: invalidExportFromThemeFiles }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export * as helpers from './helpers';
export const vars = stylex.defineVars({ color: 'red' });
`,
filename: 'myComponent.stylex.jsx',
errors: [
{ message: invalidFilenameWithoutRestrictedExports('.stylex.jsx') },
{ message: invalidExportFromThemeFiles },
],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const consts = stylex.defineConsts({ color: 'red' });
export * from './helpers';
`,
filename: 'myComponent.stylex.const.jsx',
options: [{ enforceDefineConstsExtension: true }],
errors: [{ message: invalidExportFromConstsFiles }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const themeNested = stylex.unstable_defineConstsNested({
color: { primary: 'red' },
});
export { themeNested as theme };
`,
filename: 'theme.stylex.jsx',
errors: [{ message: aliasedExport('theme', 'themeNested') }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const vars = stylex.defineVars({ color: 'red' });
export const colors = vars;
`,
filename: 'theme.stylex.jsx',
errors: [{ message: aliasedExport('colors', 'vars') }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const marker = stylex.defineMarker();
export { marker as cardMarker };
`,
filename: 'markers.stylex.jsx',
errors: [{ message: aliasedExport('cardMarker', 'marker') }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const vars = stylex.defineVars({ color: 'red' });
export const colors = vars;
`,
filename: 'theme.stylex.jsx',
options: [{ legacyAllowMixedExports: true }],
errors: [{ message: aliasedExport('colors', 'vars') }],
},
],
});

Expand Down Expand Up @@ -1083,6 +1185,17 @@ typeExportRuleTester.run(
`,
filename: 'colors.stylex.ts',
},
{
code: `
import * as stylex from '@stylexjs/stylex';
export const colors = stylex.defineVars({
primary: 'blue',
secondary: 'green',
});
export type * from './types';
`,
filename: 'colors.stylex.ts',
},
],

invalid: [
Expand All @@ -1096,6 +1209,17 @@ typeExportRuleTester.run(
filename: 'colors.stylex.ts',
errors: [{ message: invalidExportFromThemeFiles }],
},
{
code: `
import * as stylex from '@stylexjs/stylex';
type Colors = keyof typeof colors;
const helper = someFunction();
export const colors = stylex.defineVars({ primary: 'blue' });
export { type Colors, helper };
`,
filename: 'colors.stylex.ts',
errors: [{ message: invalidExportFromThemeFiles }],
},
],
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,60 @@ const stylexEnforceExtension = {
return false;
}

function isDefineVariable(name: ?string): boolean {
return (
name != null &&
(defineVarsVariables.has(name) ||
defineConstsVariables.has(name) ||
defineMarkerVariables.has(name))
);
}

function reportAliasedExport(
node: Node,
localName: string,
exportedName: string,
): void {
context.report({
node,
message: `\`${exportedName}\` is another name for \`${localName}\`. StyleX derives variable names from the export name, so importing \`${exportedName}\` references variables that are never defined. Export each \`stylex.defineVars()\`, \`stylex.defineConsts()\`, or \`stylex.defineMarker()\` result under exactly one name.`,
});
}

function checkExportSpecifiers(node: Node): void {
const specifiers = (node as any).specifiers || [];
specifiers.forEach((specifier: any) => {
if (specifier.exportKind === 'type') {
return;
}
const localName = specifier.local?.name;
const exportedName =
specifier.exported?.name ?? specifier.exported?.value;
if (
(node as any).source == null &&
localName !== exportedName &&
isDefineVariable(localName)
) {
reportAliasedExport(specifier, localName, exportedName);
} else {
hasOtherExports = true;
}
});
}

function checkExports(node: Node): void {
const declaration = (node as any).declaration;
const isDefaultExport = node.type === 'ExportDefaultDeclaration';

if (!declaration) return;
if (node.type === 'ExportAllDeclaration') {
hasOtherExports = true;
return;
}

if (!declaration) {
checkExportSpecifiers(node);
return;
}

if (isDefaultExport && checkDefaultExport(node, declaration)) {
return;
Expand Down Expand Up @@ -303,6 +352,18 @@ const stylexEnforceExtension = {
) {
defineMarkerVariables.add(decl.id.name);
}
} else if (
decl.type === 'VariableDeclarator' &&
decl.id?.type === 'Identifier' &&
decl.init?.type === 'Identifier'
) {
const exportedName = decl.id.name;
const localName = decl.init.name;
if (isDefineVariable(localName)) {
reportAliasedExport(decl, localName, exportedName);
} else {
hasOtherExports = true;
}
} else {
hasOtherExports = true;
}
Expand Down Expand Up @@ -430,7 +491,9 @@ const stylexEnforceExtension = {
}
});
},
'ExportNamedDeclaration, ExportDefaultDeclaration'(node: Node): void {
'ExportNamedDeclaration, ExportDefaultDeclaration, ExportAllDeclaration'(
node: Node,
): void {
if ((node as any).exportKind === 'type') {
return;
}
Expand Down
Loading