Skip to content

Commit

Permalink
feat: Better, granular error messages (facebook#802)
Browse files Browse the repository at this point in the history
* feat: Better, granular error messages
* fix errors
* feat: Even better errors. Fixed local function eval
* chore: another test
  • Loading branch information
nmn authored and aminaopio committed Dec 27, 2024
1 parent 3d0318f commit 0c52638
Show file tree
Hide file tree
Showing 5 changed files with 381 additions and 61 deletions.
188 changes: 179 additions & 9 deletions packages/babel-plugin/__tests__/stylex-validation-create-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,39 @@ import { transformSync } from '@babel/core';
import { messages } from '@stylexjs/shared';
import stylexPlugin from '../src/index';

// Valid string terminator sequences are BEL, ESC\, and 0x9c
const ST = '(?:\\u0007|\\u001B\\u005C|\\u009C)';
const pattern = [
`[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?${ST})`,
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))',
].join('|');

const regex = new RegExp(pattern, 'g');

function stripAnsi(string: string): string {
if (typeof string !== 'string') {
throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
}

// Even though the regex is global, we don't need to reset the `.lastIndex`
// because unlike `.exec()` and `.test()`, `.replace()` does it automatically
// and doing it manually has a performance penalty.
return string.replace(regex, '');
}

function cleanError(fn: () => mixed) {
return () => {
try {
fn();
} catch (error) {
error.message = stripAnsi(error.message);
throw error;
}
};
}

const cleanExpect = (fn: () => mixed) => expect(cleanError(fn));

function transform(source: string, opts: { [key: string]: any } = {}) {
return transformSync(source, {
filename: opts.filename,
Expand Down Expand Up @@ -163,7 +196,7 @@ describe('@stylexjs/babel-plugin', () => {
/* Properties */

test('properties must be a static value', () => {
expect(() => {
cleanExpect(() => {
transform(`
import stylex from 'stylex';
const styles = stylex.create({
Expand All @@ -172,7 +205,16 @@ describe('@stylexjs/babel-plugin', () => {
}
});
`);
}).toThrow(messages.NON_STATIC_VALUE);
}).toThrowErrorMatchingInlineSnapshot(`
"unknown file: Referenced constant is not defined.
3 | const styles = stylex.create({
4 | root: {
> 5 | [backgroundColor]: 'red',
| ^^^^^^^^^^^^^^^
6 | }
7 | });
8 | "
`);
});

/* Values */
Expand Down Expand Up @@ -223,7 +265,7 @@ describe('@stylexjs/babel-plugin', () => {
`);
}).not.toThrow();
// not string or number
expect(() => {
cleanExpect(() => {
transform(`
import stylex from 'stylex';
const styles = stylex.create({
Expand All @@ -232,7 +274,9 @@ describe('@stylexjs/babel-plugin', () => {
},
});
`);
}).toThrow(messages.ILLEGAL_PROP_ARRAY_VALUE);
}).toThrowErrorMatchingInlineSnapshot(
'"unknown file: A style array value can only contain strings or numbers."',
);
expect(() => {
transform(`
import stylex from 'stylex';
Expand All @@ -244,7 +288,7 @@ describe('@stylexjs/babel-plugin', () => {
`);
}).toThrow(messages.ILLEGAL_PROP_VALUE);
// not static
expect(() => {
cleanExpect(() => {
transform(`
import stylex from 'stylex';
const styles = stylex.create({
Expand All @@ -253,8 +297,17 @@ describe('@stylexjs/babel-plugin', () => {
}
});
`);
}).toThrow(messages.NON_STATIC_VALUE);
expect(() => {
}).toThrowErrorMatchingInlineSnapshot(`
"unknown file: Referenced constant is not defined.
3 | const styles = stylex.create({
4 | root: {
> 5 | backgroundColor: backgroundColor,
| ^^^^^^^^^^^^^^^
6 | }
7 | });
8 | "
`);
cleanExpect(() => {
transform(`
import stylex from 'stylex';
const styles = stylex.create({
Expand All @@ -263,7 +316,124 @@ describe('@stylexjs/babel-plugin', () => {
}
});
`);
}).toThrow(messages.NON_STATIC_VALUE);
}).toThrowErrorMatchingInlineSnapshot(`
"unknown file: Referenced constant is not defined.
3 | const styles = stylex.create({
4 | root: {
> 5 | backgroundColor: generateBg(),
| ^^^^^^^^^^
6 | }
7 | });
8 | "
`);
cleanExpect(() => {
transform(`
import stylex from 'stylex';
import {generateBg} from './other-file';
const styles = stylex.create({
root: {
backgroundColor: generateBg(),
}
});
`);
}).toThrowErrorMatchingInlineSnapshot(`
"unknown file: Could not resolve the path to the imported file.
Please ensure that the theme file has a .stylex.js or .stylex.ts file extension and follows the
rules for defining variariables:
https://stylexjs.com/docs/learn/theming/defining-variables/#rules-when-defining-variables
1 |
2 | import stylex from 'stylex';
> 3 | import {generateBg} from './other-file';
| ^^^^^^^^^^
4 | const styles = stylex.create({
5 | root: {
6 | backgroundColor: generateBg(),"
`);

cleanExpect(() => {
transform(`
import stylex from 'stylex';
import generateBg from './other-file';
const styles = stylex.create({
root: {
backgroundColor: generateBg(),
}
});
`);
}).toThrowErrorMatchingInlineSnapshot(`
"unknown file: There was an error when attempting to evaluate the imported file.
Please ensure that the imported file is self-contained and does not rely on dynamic behavior.
1 |
2 | import stylex from 'stylex';
> 3 | import generateBg from './other-file';
| ^^^^^^^^^^
4 | const styles = stylex.create({
5 | root: {
6 | backgroundColor: generateBg(),"
`);
});

test('[validation] can evaluate single-expr function calls', () => {
expect(() =>
transform(`
import stylex from 'stylex';
const generateBg = () => 'red';
export const styles = stylex.create({
root: {
backgroundColor: generateBg(),
}
});
`),
).not.toThrow();
});

test('[validation] can evaluate single-expr function calls in objects', () => {
let result;
expect(() => {
result = transform(`
import stylex from 'stylex';
const fns = {
generateBg: () => 'red',
};
export const styles = stylex.create({
root: {
backgroundColor: fns.generateBg(),
}
});
`);
}).not.toThrow();

expect(result).not.toBeFalsy();

expect(result?.code).toMatchInlineSnapshot(`
"import stylex from 'stylex';
const fns = {
generateBg: () => 'red'
};
export const styles = {
root: {
backgroundColor: "xrkmrrc",
$$css: true
}
};"
`);

// $FlowFixMe
expect(result?.metadata?.stylex).toMatchInlineSnapshot(`
[
[
"xrkmrrc",
{
"ltr": ".xrkmrrc{background-color:red}",
"rtl": null,
},
3000,
],
]
`);
});

test('values can reference local bindings in stylex.create()', () => {
Expand Down Expand Up @@ -334,7 +504,7 @@ describe('@stylexjs/babel-plugin', () => {
},
});
`);
}).not.toThrow(messages.INVALID_PSEUDO);
}).not.toThrow();

expect(() => {
transform(`
Expand Down
Loading

0 comments on commit 0c52638

Please sign in to comment.