From 55b48d4c77e2311e54746b85fc397e740ef9c349 Mon Sep 17 00:00:00 2001 From: a Date: Mon, 18 May 2020 22:45:23 +0200 Subject: [PATCH 1/4] Added style type errors. --- example/src/App.js | 15 +++++++++++++-- packages/babel-stylemug-plugin/src/babel.js | 15 ++++++++++++++- packages/stylemug-compiler/src/compile.js | 15 +++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/example/src/App.js b/example/src/App.js index faf1065..1d96342 100644 --- a/example/src/App.js +++ b/example/src/App.js @@ -6,10 +6,17 @@ const styles = stylemug.create({ title: { fontSize: '31px', fontFamily: 'courier', - color: '#444', + color: 'green', }, titleRed: { color: 'red', + '&:hover': 'aaa', + }, +}); + +const secondaryStyles = stylemug.create({ + color: { + color: '#444', }, }); @@ -22,7 +29,11 @@ export default function App() { return (
-

Hello World

+

+ Hello World +

); diff --git a/packages/babel-stylemug-plugin/src/babel.js b/packages/babel-stylemug-plugin/src/babel.js index 46722c6..4bebf58 100644 --- a/packages/babel-stylemug-plugin/src/babel.js +++ b/packages/babel-stylemug-plugin/src/babel.js @@ -45,7 +45,20 @@ export function babelPlugin(babel) { ); return; } - sheet = compileSchema(sheet.value); + + try { + sheet = compileSchema(sheet.value); + } catch (error) { + defineError( + local, + error && error.$$type === 'compilerError' + ? error.message + : 'The compiler failed with an unknown error.\n' + + 'Would you be so kind to report the following error?\n\n' + + error.toString() + ); + return; + } const nextLocal = t.cloneDeep(local.node); nextLocal.arguments[0] = t.objectExpression( diff --git a/packages/stylemug-compiler/src/compile.js b/packages/stylemug-compiler/src/compile.js index 869f88c..0da58cf 100644 --- a/packages/stylemug-compiler/src/compile.js +++ b/packages/stylemug-compiler/src/compile.js @@ -9,6 +9,13 @@ function hash(str) { return `${prepend}${hash}`; } +function throwError(message) { + throw { + $$type: 'compilerError', + message: message, + }; +} + export function compileSelectors(selectors, children, media) { let result = {}; @@ -66,6 +73,14 @@ export function compileSchema(schema) { const result = {}; for (let key in schema) { + if (typeof schema[key] !== 'object') { + throwError( + 'Invalid schema. Expected className ' + + key + + ' to be object, but got ' + + typeof schema[key] + ); + } result[key] = compileSelectors(schema[key], ''); } From 763e9b097a34a4c58a6377d5e19dc49a8e9f396d Mon Sep 17 00:00:00 2001 From: a Date: Tue, 19 May 2020 20:16:07 +0200 Subject: [PATCH 2/4] Added basic context asserts. --- .../compile-asserts.test.js.snap | 89 +++++++++++++++++++ .../src/__test__/compile-asserts.test.js | 39 ++++++++ .../src/__test__/compile.test.js | 21 +++-- packages/stylemug-compiler/src/compile.js | 41 +++++---- packages/stylemug-compiler/src/context.js | 18 ++++ packages/stylemug-compiler/src/static.js | 41 +++++++++ 6 files changed, 220 insertions(+), 29 deletions(-) create mode 100644 packages/stylemug-compiler/src/__test__/__snapshots__/compile-asserts.test.js.snap create mode 100644 packages/stylemug-compiler/src/__test__/compile-asserts.test.js create mode 100644 packages/stylemug-compiler/src/context.js create mode 100644 packages/stylemug-compiler/src/static.js diff --git a/packages/stylemug-compiler/src/__test__/__snapshots__/compile-asserts.test.js.snap b/packages/stylemug-compiler/src/__test__/__snapshots__/compile-asserts.test.js.snap new file mode 100644 index 0000000..04a31c8 --- /dev/null +++ b/packages/stylemug-compiler/src/__test__/__snapshots__/compile-asserts.test.js.snap @@ -0,0 +1,89 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`compile should assert an invalid rule value 1`] = ` +Array [ + Object { + "actual": null, + "expected": null, + "message": "Rule color could not be parsed due to boolean value type.", + }, +] +`; + +exports[`compile should assert an invalid sheet 1`] = ` +Object { + "default": Object { + "b765dfa68": Object { + "children": "", + "key": "backgroundColor", + "keyId": "bb7c5a6d0", + "media": undefined, + "value": "yellow", + }, + "cb34cd11f": Object { + "children": "", + "key": "color", + "keyId": "c3d7e6258", + "media": undefined, + "value": "red", + }, + }, +} +`; + +exports[`compile should assert an invalid sheet 2`] = ` +Array [ + Object { + "actual": "string", + "expected": "object", + "message": "Expected classname foo to be an object of rules.", + }, +] +`; + +exports[`compile should assert invalid pseudo classes 1`] = ` +Array [ + Object { + "actual": Array [ + ":active", + ":checked", + ":disabled", + ":empty", + ":enabled", + ":first-child", + ":first-of-type", + ":focus", + ":hover", + ":in-range", + ":invalid", + ":lang(language)", + ":last-child", + ":last-of-type", + ":link", + ":not(selector)", + ":nth-child(n)", + ":nth-last-child(n)", + ":nth-last-of-type(n)", + ":nth-of-type(n)", + ":only-of-type", + ":only-child", + ":optional", + ":out-of-range", + ":read-only", + ":read-write", + ":required", + ":root", + ":target", + ":valid", + ":visited", + ], + "expected": ":iAmAnInvalidPseudoClass", + "message": "Rule &:iAmAnInvalidPseudoClass uses an invalid CSS pseudo class.", + }, + Object { + "actual": null, + "expected": null, + "message": "Rule color could not be parsed due to boolean value type.", + }, +] +`; diff --git a/packages/stylemug-compiler/src/__test__/compile-asserts.test.js b/packages/stylemug-compiler/src/__test__/compile-asserts.test.js new file mode 100644 index 0000000..02bfb91 --- /dev/null +++ b/packages/stylemug-compiler/src/__test__/compile-asserts.test.js @@ -0,0 +1,39 @@ +import { compileSchema } from '../compile'; + +describe('compile', () => { + it('should assert an invalid sheet', () => { + const { result, reports } = compileSchema({ + default: { + color: 'red', + backgroundColor: 'yellow', + }, + // The rule below fails. + foo: 'bar', + }); + + expect(result).toMatchSnapshot(); + expect(reports).toMatchSnapshot(); + }); + + it('should assert an invalid rule value', () => { + const { reports } = compileSchema({ + default: { + color: true, + }, + }); + + expect(reports).toMatchSnapshot(); + }); + + it('should assert invalid pseudo classes', () => { + const { reports } = compileSchema({ + default: { + '&:iAmAnInvalidPseudoClass': { + color: true, + }, + }, + }); + + expect(reports).toMatchSnapshot(); + }); +}); diff --git a/packages/stylemug-compiler/src/__test__/compile.test.js b/packages/stylemug-compiler/src/__test__/compile.test.js index 4c5c775..e001048 100644 --- a/packages/stylemug-compiler/src/__test__/compile.test.js +++ b/packages/stylemug-compiler/src/__test__/compile.test.js @@ -2,7 +2,7 @@ import { compileSchema, compileSelectors } from '../compile'; describe('compile', () => { it('should compile schema', () => { - const result = compileSchema({ + const { result } = compileSchema({ default: { color: 'red', backgroundColor: 'yellow', @@ -18,16 +18,8 @@ describe('compile', () => { expect(result).toMatchSnapshot(); }); - it('should compile selectors with number as value', () => { - const result = compileSelectors({ - fontSize: 12, - }); - - expect(result).toMatchSnapshot(); - }); - it('should compile nested selectors', () => { - const result = compileSchema({ + const { result } = compileSchema({ default: { color: 'red', @@ -41,7 +33,7 @@ describe('compile', () => { }); it('should compile nested media query', () => { - const result = compileSchema({ + const { result } = compileSchema({ default: { color: 'red', @@ -53,4 +45,11 @@ describe('compile', () => { expect(result).toMatchSnapshot(); }); + + it('should compile selectors with number as value', () => { + const result = compileSelectors({ + fontSize: 12, + }); + expect(result).toMatchSnapshot(); + }); }); diff --git a/packages/stylemug-compiler/src/compile.js b/packages/stylemug-compiler/src/compile.js index 0da58cf..57871ce 100644 --- a/packages/stylemug-compiler/src/compile.js +++ b/packages/stylemug-compiler/src/compile.js @@ -1,6 +1,8 @@ import fnv1a from 'fnv1a'; import { save as extractorSave } from './extractor'; import { extractShorthands } from './shorthands'; +import { createContext } from './context'; +import { PseudoClasses, PseudoElements } from './static'; function hash(str) { let hash = fnv1a(str).toString(16); @@ -9,14 +11,7 @@ function hash(str) { return `${prepend}${hash}`; } -function throwError(message) { - throw { - $$type: 'compilerError', - message: message, - }; -} - -export function compileSelectors(selectors, children, media) { +export function compileSelectors(selectors, children, media, context) { let result = {}; selectors = extractShorthands(selectors); @@ -25,20 +20,32 @@ export function compileSelectors(selectors, children, media) { const value = selectors[key]; switch (typeof value) { + default: + context.report('Invalid type for ' + key); + continue; + case 'object': // @media if (/^@/.test(key)) { result = Object.assign( result, - compileSelectors(value, children, key) + compileSelectors(value, children, key, context) ); } // &:focus else { const child = key.replace(/&/g, ''); + + if ( + !PseudoClasses.includes(child) && + !PseudoElements.includes(child) + ) { + context.report(key + ' is an invalid CSS pseudo class / element'); + } + result = Object.assign( result, - compileSelectors(value, children + child, media) + compileSelectors(value, children + child, media, context) ); } continue; @@ -71,18 +78,16 @@ export function compileSelectors(selectors, children, media) { export function compileSchema(schema) { const result = {}; + const context = createContext(); for (let key in schema) { if (typeof schema[key] !== 'object') { - throwError( - 'Invalid schema. Expected className ' + - key + - ' to be object, but got ' + - typeof schema[key] - ); + context.report('Classname with key ' + key + ' is not an object'); + continue; } - result[key] = compileSelectors(schema[key], ''); + + result[key] = compileSelectors(schema[key], '', undefined, context); } - return result; + return { result, reports: context.getReports() }; } diff --git a/packages/stylemug-compiler/src/context.js b/packages/stylemug-compiler/src/context.js new file mode 100644 index 0000000..c813de5 --- /dev/null +++ b/packages/stylemug-compiler/src/context.js @@ -0,0 +1,18 @@ +export function createContext() { + const reports = []; + + function report(message) { + reports.push({ + message, + }); + } + + function getReports() { + return reports; + } + + return { + report, + getReports, + }; +} diff --git a/packages/stylemug-compiler/src/static.js b/packages/stylemug-compiler/src/static.js new file mode 100644 index 0000000..93c8d6d --- /dev/null +++ b/packages/stylemug-compiler/src/static.js @@ -0,0 +1,41 @@ +export const PseudoClasses = [ + ':active', + ':checked', + ':disabled', + ':empty', + ':enabled', + ':first-child', + ':first-of-type', + ':focus', + ':hover', + ':in-range', + ':invalid', + ':lang(language)', + ':last-child', + ':last-of-type', + ':link', + ':not(selector)', + ':nth-child(n)', + ':nth-last-child(n)', + ':nth-last-of-type(n)', + ':nth-of-type(n)', + ':only-of-type', + ':only-child', + ':optional', + ':out-of-range', + ':read-only', + ':read-write', + ':required', + ':root', + ':target', + ':valid', + ':visited', +]; + +export const PseudoElements = [ + '::after', + '::before', + '::first-letter', + '::first-line', + '::selection', +]; From 9c7fae1a0729e1e9e07dfbb71f1c5944638f2db6 Mon Sep 17 00:00:00 2001 From: a Date: Tue, 19 May 2020 20:16:39 +0200 Subject: [PATCH 3/4] Renamed asserts to context. --- .../compile-asserts.test.js.snap | 89 ------------------- .../compile-context.test.js.snap | 49 ++++++++++ ...sserts.test.js => compile-context.test.js} | 0 3 files changed, 49 insertions(+), 89 deletions(-) delete mode 100644 packages/stylemug-compiler/src/__test__/__snapshots__/compile-asserts.test.js.snap create mode 100644 packages/stylemug-compiler/src/__test__/__snapshots__/compile-context.test.js.snap rename packages/stylemug-compiler/src/__test__/{compile-asserts.test.js => compile-context.test.js} (100%) diff --git a/packages/stylemug-compiler/src/__test__/__snapshots__/compile-asserts.test.js.snap b/packages/stylemug-compiler/src/__test__/__snapshots__/compile-asserts.test.js.snap deleted file mode 100644 index 04a31c8..0000000 --- a/packages/stylemug-compiler/src/__test__/__snapshots__/compile-asserts.test.js.snap +++ /dev/null @@ -1,89 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`compile should assert an invalid rule value 1`] = ` -Array [ - Object { - "actual": null, - "expected": null, - "message": "Rule color could not be parsed due to boolean value type.", - }, -] -`; - -exports[`compile should assert an invalid sheet 1`] = ` -Object { - "default": Object { - "b765dfa68": Object { - "children": "", - "key": "backgroundColor", - "keyId": "bb7c5a6d0", - "media": undefined, - "value": "yellow", - }, - "cb34cd11f": Object { - "children": "", - "key": "color", - "keyId": "c3d7e6258", - "media": undefined, - "value": "red", - }, - }, -} -`; - -exports[`compile should assert an invalid sheet 2`] = ` -Array [ - Object { - "actual": "string", - "expected": "object", - "message": "Expected classname foo to be an object of rules.", - }, -] -`; - -exports[`compile should assert invalid pseudo classes 1`] = ` -Array [ - Object { - "actual": Array [ - ":active", - ":checked", - ":disabled", - ":empty", - ":enabled", - ":first-child", - ":first-of-type", - ":focus", - ":hover", - ":in-range", - ":invalid", - ":lang(language)", - ":last-child", - ":last-of-type", - ":link", - ":not(selector)", - ":nth-child(n)", - ":nth-last-child(n)", - ":nth-last-of-type(n)", - ":nth-of-type(n)", - ":only-of-type", - ":only-child", - ":optional", - ":out-of-range", - ":read-only", - ":read-write", - ":required", - ":root", - ":target", - ":valid", - ":visited", - ], - "expected": ":iAmAnInvalidPseudoClass", - "message": "Rule &:iAmAnInvalidPseudoClass uses an invalid CSS pseudo class.", - }, - Object { - "actual": null, - "expected": null, - "message": "Rule color could not be parsed due to boolean value type.", - }, -] -`; diff --git a/packages/stylemug-compiler/src/__test__/__snapshots__/compile-context.test.js.snap b/packages/stylemug-compiler/src/__test__/__snapshots__/compile-context.test.js.snap new file mode 100644 index 0000000..bd0c238 --- /dev/null +++ b/packages/stylemug-compiler/src/__test__/__snapshots__/compile-context.test.js.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`compile should assert an invalid rule value 1`] = ` +Array [ + Object { + "message": "Invalid type for color", + }, +] +`; + +exports[`compile should assert an invalid sheet 1`] = ` +Object { + "default": Object { + "b765dfa68": Object { + "children": "", + "key": "backgroundColor", + "keyId": "bb7c5a6d0", + "media": undefined, + "value": "yellow", + }, + "cb34cd11f": Object { + "children": "", + "key": "color", + "keyId": "c3d7e6258", + "media": undefined, + "value": "red", + }, + }, +} +`; + +exports[`compile should assert an invalid sheet 2`] = ` +Array [ + Object { + "message": "Classname with key foo is not an object", + }, +] +`; + +exports[`compile should assert invalid pseudo classes 1`] = ` +Array [ + Object { + "message": "&:iAmAnInvalidPseudoClass is an invalid CSS pseudo class / element", + }, + Object { + "message": "Invalid type for color", + }, +] +`; diff --git a/packages/stylemug-compiler/src/__test__/compile-asserts.test.js b/packages/stylemug-compiler/src/__test__/compile-context.test.js similarity index 100% rename from packages/stylemug-compiler/src/__test__/compile-asserts.test.js rename to packages/stylemug-compiler/src/__test__/compile-context.test.js From b9e2357d7bc5d4ed2c0529cab5425eac66a914ad Mon Sep 17 00:00:00 2001 From: a Date: Tue, 19 May 2020 21:25:45 +0200 Subject: [PATCH 4/4] Added error formatting. --- example/package.json | 3 +- example/src/App.js | 5 +- .../__test__/__snapshots__/babel.test.js.snap | 18 +++- .../src/__test__/babel.test.js | 43 ++++++-- packages/babel-stylemug-plugin/src/babel.js | 63 +++++++----- .../compile-context.test.js.snap | 14 +++ .../src/__test__/compile-context.test.js | 13 +++ packages/stylemug-compiler/src/compile.js | 98 ++++++++++--------- .../stylemug/src/__test__/runtime.test.js | 15 --- packages/stylemug/src/__test__/warn.test.js | 10 -- packages/stylemug/src/index.js | 17 +--- packages/stylemug/src/warn.js | 18 ++-- scripts/dev | 2 +- 13 files changed, 193 insertions(+), 126 deletions(-) delete mode 100644 packages/stylemug/src/__test__/warn.test.js diff --git a/example/package.json b/example/package.json index 34b696b..e6c2851 100644 --- a/example/package.json +++ b/example/package.json @@ -4,7 +4,8 @@ "main": "index.js", "license": "MIT", "scripts": { - "start": "webpack-dev-server" + "start": "webpack-dev-server", + "build": "webpack --mode development" }, "dependencies": { "react": "^16.13.1", diff --git a/example/src/App.js b/example/src/App.js index 1d96342..5b39b1b 100644 --- a/example/src/App.js +++ b/example/src/App.js @@ -3,6 +3,7 @@ import stylemug from 'stylemug'; import { globalStyles } from './globals'; const styles = stylemug.create({ + foo: 'bar', title: { fontSize: '31px', fontFamily: 'courier', @@ -10,7 +11,9 @@ const styles = stylemug.create({ }, titleRed: { color: 'red', - '&:hover': 'aaa', + '&:hoverr': { + color: 'red', + }, }, }); diff --git a/packages/babel-stylemug-plugin/src/__test__/__snapshots__/babel.test.js.snap b/packages/babel-stylemug-plugin/src/__test__/__snapshots__/babel.test.js.snap index 25c692f..b4e37d3 100644 --- a/packages/babel-stylemug-plugin/src/__test__/__snapshots__/babel.test.js.snap +++ b/packages/babel-stylemug-plugin/src/__test__/__snapshots__/babel.test.js.snap @@ -1,5 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`babel plugin - reports should replace create argument 1`] = ` +"\\"use strict\\"; + +var _stylemug = _interopRequireDefault(require(\\"stylemug\\")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \\"default\\": obj }; } + +var styles = _stylemug[\\"default\\"].create({}, { + sourceLinesRange: \\"In lines 4 to 4\\", + messages: [\\"mockError1\\", \\"mockError2\\"] +});" +`; + exports[`babel plugin should error when failed to resolve 1`] = ` "\\"use strict\\"; @@ -13,7 +26,10 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope var styles = _stylemug[\\"default\\"].create(_defineProperty({}, _mock.selector, { color: 'red' -}), \\"Failed to evaluate the following stylesheet: \\\\n\\\\nstylemug.create({\\\\n [selector]: {\\\\n color: 'red'\\\\n }\\\\n})\\\\n\\\\nMake sure your stylesheet is statically defined.\\");" +}), { + sourceLinesRange: \\"In lines 5 to 9\\", + messages: [\\"Failed to evaluate the following stylesheet: \\\\n\\\\nstylemug.create({\\\\n [selector]: {\\\\n color: 'red'\\\\n }\\\\n})\\\\n\\\\nMake sure your stylesheet is statically defined.\\"] +});" `; exports[`babel plugin should replace create argument 1`] = ` diff --git a/packages/babel-stylemug-plugin/src/__test__/babel.test.js b/packages/babel-stylemug-plugin/src/__test__/babel.test.js index 320457a..cfe6b16 100644 --- a/packages/babel-stylemug-plugin/src/__test__/babel.test.js +++ b/packages/babel-stylemug-plugin/src/__test__/babel.test.js @@ -1,17 +1,25 @@ import { transform } from '@babel/core'; +import { compileSchema } from 'stylemug-compiler'; import { babelPlugin as plugin } from '../babel'; jest.mock('stylemug-compiler', () => ({ - compileSchema: () => ({ - className: { - hash: { - keyId: 'id', - }, - }, - }), + compileSchema: jest.fn(), })); describe('babel plugin', () => { + beforeEach(() => { + compileSchema.mockImplementationOnce(() => ({ + result: { + className: { + hash: { + keyId: 'id', + }, + }, + }, + reports: [], + })); + }); + it('should replace create argument', () => { const example = ` import stylemug from 'stylemug'; @@ -73,3 +81,24 @@ describe('babel plugin', () => { expect(code).toMatchSnapshot(); }); }); + +describe('babel plugin - reports', () => { + it('should replace create argument', () => { + compileSchema.mockImplementationOnce(() => ({ + result: {}, + reports: [{ message: 'mockError1' }, { message: 'mockError2' }], + })); + + const example = ` + import stylemug from 'stylemug'; + + const styles = stylemug.create({}); + `; + + const { code } = transform(example, { + plugins: [plugin], + }); + + expect(code).toMatchSnapshot(); + }); +}); diff --git a/packages/babel-stylemug-plugin/src/babel.js b/packages/babel-stylemug-plugin/src/babel.js index 4bebf58..a284ff9 100644 --- a/packages/babel-stylemug-plugin/src/babel.js +++ b/packages/babel-stylemug-plugin/src/babel.js @@ -5,9 +5,39 @@ import { compileSchema } from 'stylemug-compiler'; export function babelPlugin(babel) { const t = babel.types; - function defineError(path, msg) { + function wrapReports(path, reports) { + if (!Array.isArray(reports)) { + reports = [reports]; + } + if (!reports.length) { + return; + } + const node = t.cloneDeep(path.node); - node.arguments[1] = t.stringLiteral(msg); + const objectChilds = [ + t.objectProperty( + t.identifier('sourceLinesRange'), + t.stringLiteral( + 'In lines ' + node.loc.start.line + ' to ' + node.loc.end.line + ) + ), + t.objectProperty( + t.identifier('messages'), + t.arrayExpression( + reports.map((report) => t.stringLiteral(report.message)) + ) + ), + ]; + if (path.hub.file.opts.filename) { + objectChilds.push( + t.objectProperty( + t.identifier('fileName'), + t.stringLiteral(path.hub.file.opts.filename || 'unknown') + ) + ); + } + + node.arguments[1] = t.objectExpression(objectChilds); path.replaceWith(node); } @@ -34,35 +64,24 @@ export function babelPlugin(babel) { references.forEach((reference) => { const local = reference.parentPath.parentPath; - let sheet = evaluateSimple(local.get('arguments')[0]); + const sheet = evaluateSimple(local.get('arguments')[0]); if (!sheet.confident) { - defineError( - local, - 'Failed to evaluate the following stylesheet: \n\n' + + wrapReports(local, { + message: + 'Failed to evaluate the following stylesheet: \n\n' + local.toString() + '\n\n' + - 'Make sure your stylesheet is statically defined.' - ); + 'Make sure your stylesheet is statically defined.', + }); return; } - try { - sheet = compileSchema(sheet.value); - } catch (error) { - defineError( - local, - error && error.$$type === 'compilerError' - ? error.message - : 'The compiler failed with an unknown error.\n' + - 'Would you be so kind to report the following error?\n\n' + - error.toString() - ); - return; - } + const { result, reports } = compileSchema(sheet.value); + wrapReports(local, reports); const nextLocal = t.cloneDeep(local.node); nextLocal.arguments[0] = t.objectExpression( - Object.entries(sheet).map(([name, rules]) => { + Object.entries(result).map(([name, rules]) => { return t.objectProperty( t.identifier(name), t.objectExpression( diff --git a/packages/stylemug-compiler/src/__test__/__snapshots__/compile-context.test.js.snap b/packages/stylemug-compiler/src/__test__/__snapshots__/compile-context.test.js.snap index bd0c238..ee132ae 100644 --- a/packages/stylemug-compiler/src/__test__/__snapshots__/compile-context.test.js.snap +++ b/packages/stylemug-compiler/src/__test__/__snapshots__/compile-context.test.js.snap @@ -47,3 +47,17 @@ Array [ }, ] `; + +exports[`compile should report multiple errors 1`] = ` +Array [ + Object { + "message": "Classname with key foo is not an object", + }, + Object { + "message": "&:iAmAnInvalidPseudoClass is an invalid CSS pseudo class / element", + }, + Object { + "message": "Invalid type for color", + }, +] +`; diff --git a/packages/stylemug-compiler/src/__test__/compile-context.test.js b/packages/stylemug-compiler/src/__test__/compile-context.test.js index 02bfb91..ceddd11 100644 --- a/packages/stylemug-compiler/src/__test__/compile-context.test.js +++ b/packages/stylemug-compiler/src/__test__/compile-context.test.js @@ -36,4 +36,17 @@ describe('compile', () => { expect(reports).toMatchSnapshot(); }); + + it('should report multiple errors', () => { + const { reports } = compileSchema({ + foo: 'bar', + default: { + '&:iAmAnInvalidPseudoClass': { + color: true, + }, + }, + }); + + expect(reports).toMatchSnapshot(); + }); }); diff --git a/packages/stylemug-compiler/src/compile.js b/packages/stylemug-compiler/src/compile.js index 57871ce..542cf4b 100644 --- a/packages/stylemug-compiler/src/compile.js +++ b/packages/stylemug-compiler/src/compile.js @@ -18,59 +18,63 @@ export function compileSelectors(selectors, children, media, context) { for (let key in selectors) { const value = selectors[key]; + const type = typeof value; - switch (typeof value) { - default: - context.report('Invalid type for ' + key); + // @media + if (/^@/.test(key)) { + if (type !== 'object') { + context.report('Rule with key ' + key + ' is not an object'); continue; + } + result = Object.assign( + result, + compileSelectors(value, children, key, context) + ); + continue; + } - case 'object': - // @media - if (/^@/.test(key)) { - result = Object.assign( - result, - compileSelectors(value, children, key, context) - ); - } - // &:focus - else { - const child = key.replace(/&/g, ''); - - if ( - !PseudoClasses.includes(child) && - !PseudoElements.includes(child) - ) { - context.report(key + ' is an invalid CSS pseudo class / element'); - } - - result = Object.assign( - result, - compileSelectors(value, children + child, media, context) - ); - } - continue; + // &:focus + // &::after + if (/^&/.test(key)) { + const child = key.replace(/&/g, ''); - case 'number': - case 'string': - const mediaKey = media || ''; - const className = hash(key + value + children + mediaKey); - - const entry = { - // Provide a key id to match selectors setting the same key, - // for example, when for a given key, two classes apply with both - // backgroundColor as propery, we can filter out the first one by specificity. - keyId: hash(key + children + mediaKey), - key, - value, - children, - media, - }; - - extractorSave(className, entry); - - result[className] = entry; + if (!PseudoClasses.includes(child) && !PseudoElements.includes(child)) { + context.report(key + ' is an invalid CSS pseudo class / element'); + } + if (type !== 'object') { + context.report('Rule with key ' + key + ' is not an object'); continue; + } + + result = Object.assign( + result, + compileSelectors(value, children + child, media, context) + ); + continue; + } + + if (type === 'number' || type === 'string') { + const mediaKey = media || ''; + const className = hash(key + value + children + mediaKey); + + const entry = { + // Provide a key id to match selectors setting the same key, + // for example, when for a given key, two classes apply with both + // backgroundColor as propery, we can filter out the first one by specificity. + keyId: hash(key + children + mediaKey), + key, + value, + children, + media, + }; + + extractorSave(className, entry); + + result[className] = entry; + continue; } + + context.report('Invalid type for ' + key); } return result; diff --git a/packages/stylemug/src/__test__/runtime.test.js b/packages/stylemug/src/__test__/runtime.test.js index a101e40..b3fd21b 100644 --- a/packages/stylemug/src/__test__/runtime.test.js +++ b/packages/stylemug/src/__test__/runtime.test.js @@ -43,21 +43,6 @@ describe('runtime', () => { }); }); - describe('resolver warnings', () => { - it('should warn when a lookup failed', () => { - const styles = runtime.create({ - foo: { - color: 'red', - }, - }); - styles('unknown'); - - expect(warn).toHaveBeenCalledWith( - 'The class name "unknown" does not exist in your stylesheet. Check your stylemug.create({}) definition.' - ); - }); - }); - describe('compiler warnings', () => { it('should warn if a compiler error is thrown', () => { runtime.create( diff --git a/packages/stylemug/src/__test__/warn.test.js b/packages/stylemug/src/__test__/warn.test.js deleted file mode 100644 index 3a6fb74..0000000 --- a/packages/stylemug/src/__test__/warn.test.js +++ /dev/null @@ -1,10 +0,0 @@ -import warn from '../warn'; - -global.console.warn = jest.fn(); - -it('should log a warning message', () => { - warn('Something bad happened'); - expect(global.console.warn).toHaveBeenCalledWith( - '[stylemug] Something bad happened' - ); -}); diff --git a/packages/stylemug/src/index.js b/packages/stylemug/src/index.js index 935ec74..daa7c9d 100644 --- a/packages/stylemug/src/index.js +++ b/packages/stylemug/src/index.js @@ -1,14 +1,9 @@ import warn from './warn'; -const noop = () => {}; - export default { create(schema, error) { - if (error) { - if (__DEV__) { - warn(error); - } - return noop; + if (__DEV__ && error) { + warn(error); } const resolver = (...classNames) => { @@ -24,14 +19,6 @@ export default { maps.push(className); } if (typeof className === 'string') { - if (__DEV__ && !schema[className]) { - warn( - 'The class name "' + - className + - '" does not exist in your stylesheet. Check your ' + - 'stylemug.create({}) definition.' - ); - } maps.push(schema[className]); } } diff --git a/packages/stylemug/src/warn.js b/packages/stylemug/src/warn.js index ec6e9eb..69ce069 100644 --- a/packages/stylemug/src/warn.js +++ b/packages/stylemug/src/warn.js @@ -1,9 +1,15 @@ -const warnings = {}; +export default function warn(error) { + let message = 'There are warnings in your stylesheet:\n\n'; -export default function warn(str) { - if (warnings[warn]) { - return; + if (error.fileName) { + message += error.fileName + '\n'; + message += ' ' + error.sourceLinesRange; + message += '\n\n'; } - warnings[warn] = true; - console.warn('[stylemug] ' + str); + + error.messages.forEach((msg, i) => { + message += i + 1 + ') ' + msg + '\n'; + }); + + console.warn('[stylemug]', message); } diff --git a/scripts/dev b/scripts/dev index 120c50a..6b36ed0 100755 --- a/scripts/dev +++ b/scripts/dev @@ -7,7 +7,7 @@ spawn('scripts/build', { stdio: 'inherit', }); -spawn('yarn', ['start'], { +spawn('yarn', ['build'], { stdio: 'inherit', cwd: path.resolve('./example'), });