diff --git a/.tape.js b/.tape.js index b93b8d4..ee3757c 100644 --- a/.tape.js +++ b/.tape.js @@ -105,3 +105,20 @@ test(rule, { accept, reject }); + +accept = [ + { code: '@import "import-custom-properties-absolute.css"; body { background-color: var(--brand-red); background: var(--brand-green); }' } +]; +reject = []; + +test(rule, { + ruleName, + config: [true, { + resolver: { + paths: './test' + } + }], + skipBasicChecks, + accept, + reject +}); diff --git a/README.md b/README.md index 0e5cbe7..daaa138 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,28 @@ objects. } ``` +### resolver + +Use this option to configure how the rule solve paths of `@import` rules. + +```js +// .stylelintrc +{ + "plugins": [ + "stylelint-value-no-unknown-custom-properties" + ], + "rules": { + "csstools/value-no-unknown-custom-properties": [true, { + "resolver": { + "extensions": [".css"], // => default to [".css"] + "paths": ["./assets/css", "./static/css"] // => paths to look for files, default to [] + "moduleDirectories": ["node_modules"] // => modules folder to look for files, default to ["node_modules"] + } + }] + } +} +``` + [cli-img]: https://img.shields.io/travis/csstools/stylelint-value-no-unknown-custom-properties.svg [cli-url]: https://travis-ci.org/csstools/stylelint-value-no-unknown-custom-properties [git-img]: https://img.shields.io/badge/support-chat-blue.svg diff --git a/package.json b/package.json index 1b52811..7a60d18 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "node": ">=10.0.0" }, "dependencies": { - "postcss-values-parser": "^3.2.1" + "postcss-values-parser": "^3.2.1", + "resolve": "^1.20.0" }, "devDependencies": { "@babel/core": "^7.9.6", diff --git a/src/index.js b/src/index.js index c0e6919..5baddf8 100644 --- a/src/index.js +++ b/src/index.js @@ -7,10 +7,11 @@ import ruleName from './lib/rule-name'; export default stylelint.createPlugin(ruleName, (method, opts) => { // sources to import custom selectors from const importFrom = [].concat(Object(opts).importFrom || []); + const resolver = Object(opts).resolver || {}; // promise any custom selectors are imported const customPropertiesPromise = isMethodEnabled(method) - ? getCustomPropertiesFromImports(importFrom) + ? getCustomPropertiesFromImports(importFrom, resolver) : {}; return async (root, result) => { @@ -26,7 +27,7 @@ export default stylelint.createPlugin(ruleName, (method, opts) => { // all custom properties from the file and imports const customProperties = Object.assign( await customPropertiesPromise, - await getCustomPropertiesFromRoot(root) + await getCustomPropertiesFromRoot(root, resolver) ); // validate the css root diff --git a/src/lib/get-custom-properties-from-imports.js b/src/lib/get-custom-properties-from-imports.js index efc3331..be98c9f 100644 --- a/src/lib/get-custom-properties-from-imports.js +++ b/src/lib/get-custom-properties-from-imports.js @@ -6,11 +6,11 @@ import getCustomPropertiesFromRoot from './get-custom-properties-from-root'; /* Get Custom Properties from CSS File /* ========================================================================== */ -async function getCustomPropertiesFromCSSFile(from) { +async function getCustomPropertiesFromCSSFile(from, resolver) { const css = await readFile(from); const root = postcss.parse(css, { from }); - return await getCustomPropertiesFromRoot(root); + return await getCustomPropertiesFromRoot(root, resolver); } /* Get Custom Properties from Object @@ -47,7 +47,7 @@ async function getCustomPropertiesFromJSFile(from) { /* Get Custom Properties from Sources /* ========================================================================== */ -export default function getCustomPropertiesFromSources(sources) { +export default function getCustomPropertiesFromSources(sources, resolver) { return sources.map(source => { if (source instanceof Promise) { return source; @@ -74,7 +74,7 @@ export default function getCustomPropertiesFromSources(sources) { const { type, from } = await source; if (type === 'css') { - return Object.assign(await customProperties, await getCustomPropertiesFromCSSFile(from)); + return Object.assign(await customProperties, await getCustomPropertiesFromCSSFile(from, resolver)); } if (type === 'js') { diff --git a/src/lib/get-custom-properties-from-root.js b/src/lib/get-custom-properties-from-root.js index 1a0654e..c5ef301 100644 --- a/src/lib/get-custom-properties-from-root.js +++ b/src/lib/get-custom-properties-from-root.js @@ -1,9 +1,10 @@ import { promises as fs } from 'fs'; import path from 'path'; import postcss from 'postcss'; +import { resolveId } from './resolve-id' // return custom selectors from the css root, conditionally removing them -export default async function getCustomPropertiesFromRoot(root) { +export default async function getCustomPropertiesFromRoot(root, resolver) { // initialize custom selectors let customProperties = {}; @@ -17,8 +18,19 @@ export default async function getCustomPropertiesFromRoot(root) { const importPromises = []; root.walkAtRules('import', atRule => { const fileName = atRule.params.replace(/['|"]/g, ''); - const resolvedFileName = path.resolve(sourceDir, fileName); - importPromises.push(getCustomPropertiesFromCSSFile(resolvedFileName)); + + if (path.isAbsolute(fileName)) { + importPromises.push(getCustomPropertiesFromCSSFile(fileName, resolver)); + } else { + const promise = resolveId(fileName, sourceDir, { + paths: resolver.paths, + extensions: resolver.extensions, + moduleDirectories: resolver.moduleDirectories + }) + .then((filePath) => getCustomPropertiesFromCSSFile(filePath, resolver)) + .catch(() => {}) + importPromises.push(promise) + } }); (await Promise.all(importPromises)).forEach(propertiesFromImport => { @@ -41,12 +53,12 @@ export default async function getCustomPropertiesFromRoot(root) { const customPropertyRegExp = /^--[A-z][\w-]*$/; -async function getCustomPropertiesFromCSSFile(from) { +async function getCustomPropertiesFromCSSFile(from, resolver) { try { const css = await fs.readFile(from, 'utf8'); const root = postcss.parse(css, { from }); - return await getCustomPropertiesFromRoot(root); + return await getCustomPropertiesFromRoot(root, resolver); } catch (e) { return {}; } diff --git a/src/lib/resolve-id.js b/src/lib/resolve-id.js new file mode 100644 index 0000000..7da0b07 --- /dev/null +++ b/src/lib/resolve-id.js @@ -0,0 +1,19 @@ +import resolve from 'resolve' + +export function resolveId(id, basedir, { + paths = [], + moduleDirectories = ['node_modules'], + extensions = ['.css'] + } = {} +) { + const resolveOpts = { + basedir, + moduleDirectory: moduleDirectories, + paths, + extensions, + preserveSymlinks: false, + } + return new Promise((res, rej) => { + resolve(id, resolveOpts, (err, resolvedPath) => err ? rej(err) : res(resolvedPath)) + }) +} diff --git a/test/import-custom-properties-absolute.css b/test/import-custom-properties-absolute.css new file mode 100644 index 0000000..cad6ff3 --- /dev/null +++ b/test/import-custom-properties-absolute.css @@ -0,0 +1,5 @@ +@import 'import-custom-properties-2.css'; + +:root { + --brand-red: red; +}