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

Add loader options validation #234

Merged
merged 1 commit into from Sep 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 11 additions & 6 deletions src/index.js
Expand Up @@ -4,18 +4,23 @@ import stylus from 'stylus';

import clone from 'clone';

import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';

import schema from './options.json';
import createEvaluator from './evaluator';
import { getOptions, isObject, castArray } from './utils';
import { isObject, castArray } from './utils';
import resolver from './lib/resolver';

export default async function stylusLoader(source) {
const callback = this.async();
const options = getOptions(this);

// get options passed to loader
const loaderOptions = getOptions(this);
validateOptions(schema, options, {
name: 'Stylus Loader',
baseDataPath: 'options',
});

// clone loader options to avoid modifying this.query
const options = loaderOptions ? { ...loaderOptions } : {};
const callback = this.async();

const stylusOptions = clone(options.stylusOptions) || {};

Expand Down
8 changes: 7 additions & 1 deletion src/options.json
@@ -1,7 +1,13 @@
{
"type": "object",
"properties": {
"name": {
"stylusOptions": {
"description": "Options to pass through to `Stylus` (https://github.com/webpack-contrib/stylus-loader#stylusoptions).",
"type": "object",
"additionalProperties": true
},
"sourceMap": {
"description": "Enables/Disables generation of source maps (https://github.com/webpack-contrib/stylus-loader#sourcemap).",
"type": "boolean"
}
},
Expand Down
9 changes: 0 additions & 9 deletions src/utils.js
@@ -1,12 +1,3 @@
export function getOptions(context) {
if (typeof context.getOptions === 'function') {
return context.getOptions();
}

// eslint-disable-next-line global-require
return require('loader-utils').getOptions(context);
}

export function isObject(value) {
return typeof value === 'object' && value !== null;
}
Expand Down
90 changes: 90 additions & 0 deletions test/__snapshots__/validate-options.test.js.snap
@@ -0,0 +1,90 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`validate options should throw an error on the "sourceMap" option with "string" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options.sourceMap should be a boolean.
-> Enables/Disables generation of source maps (https://github.com/webpack-contrib/stylus-loader#sourcemap)."
`;

exports[`validate options should throw an error on the "stylusOptions" option with "[]" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options.stylusOptions should be an object:
object { … }
-> Options to pass through to \`Stylus\` (https://github.com/webpack-contrib/stylus-loader#stylusoptions)."
`;

exports[`validate options should throw an error on the "stylusOptions" option with "1" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options.stylusOptions should be an object:
object { … }
-> Options to pass through to \`Stylus\` (https://github.com/webpack-contrib/stylus-loader#stylusoptions)."
`;

exports[`validate options should throw an error on the "stylusOptions" option with "false" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options.stylusOptions should be an object:
object { … }
-> Options to pass through to \`Stylus\` (https://github.com/webpack-contrib/stylus-loader#stylusoptions)."
`;

exports[`validate options should throw an error on the "stylusOptions" option with "test" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options.stylusOptions should be an object:
object { … }
-> Options to pass through to \`Stylus\` (https://github.com/webpack-contrib/stylus-loader#stylusoptions)."
`;

exports[`validate options should throw an error on the "stylusOptions" option with "true" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options.stylusOptions should be an object:
object { … }
-> Options to pass through to \`Stylus\` (https://github.com/webpack-contrib/stylus-loader#stylusoptions)."
`;

exports[`validate options should throw an error on the "unknown" option with "/test/" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap? }"
`;

exports[`validate options should throw an error on the "unknown" option with "[]" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap? }"
`;

exports[`validate options should throw an error on the "unknown" option with "{"foo":"bar"}" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap? }"
`;

exports[`validate options should throw an error on the "unknown" option with "{}" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap? }"
`;

exports[`validate options should throw an error on the "unknown" option with "1" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap? }"
`;

exports[`validate options should throw an error on the "unknown" option with "false" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap? }"
`;

exports[`validate options should throw an error on the "unknown" option with "test" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap? }"
`;

exports[`validate options should throw an error on the "unknown" option with "true" value 1`] = `
"Invalid options object. Stylus Loader has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { stylusOptions?, sourceMap? }"
`;
74 changes: 74 additions & 0 deletions test/validate-options.test.js
@@ -0,0 +1,74 @@
import { getCompiler, compile } from './helpers/index';

describe('validate options', () => {
const tests = {
stylusOptions: {
success: [
{},
{ resolveCss: true },
{ includeCSS: false },
{
define: {
$development: process.env.NODE_ENV === 'development',
},
},
],
failure: [1, true, false, 'test', []],
},
sourceMap: {
success: [true, false],
failure: ['string'],
},
unknown: {
success: [],
failure: [1, true, false, 'test', /test/, [], {}, { foo: 'bar' }],
},
};

function stringifyValue(value) {
if (
Array.isArray(value) ||
(value && typeof value === 'object' && value.constructor === Object)
) {
return JSON.stringify(value);
}

return value;
}

async function createTestCase(key, value, type) {
it(`should ${
type === 'success' ? 'successfully validate' : 'throw an error on'
} the "${key}" option with "${stringifyValue(value)}" value`, async () => {
const compiler = getCompiler('./basic.styl', {
[key]: value,
});
let stats;

try {
stats = await compile(compiler);
} finally {
if (type === 'success') {
expect(stats.hasErrors()).toBe(false);
} else if (type === 'failure') {
const {
compilation: { errors },
} = stats;

expect(errors).toHaveLength(1);
expect(() => {
throw new Error(errors[0].error.message);
}).toThrowErrorMatchingSnapshot();
}
}
});
}

for (const [key, values] of Object.entries(tests)) {
for (const type of Object.keys(values)) {
for (const value of values[type]) {
createTestCase(key, value, type);
}
}
}
});