From dfbc1a1f58f3c7957eab0d7d8e0f2a7f766eb539 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 22 May 2026 10:00:26 +0000 Subject: [PATCH 1/5] feat: add JSDoc types and TypeScript declaration generation Annotate the source with JSDoc, add a `tsconfig.json`, and emit TypeScript declarations to `types/`. The package now exposes types via the `exports`/`types` fields, mirroring how `sass-loader` ships them. --- .changeset/add-jsdoc-types.md | 5 + bench/fixtures/imports/testLoader.js | 4 + bench/index.js | 4 + package-lock.json | 39 +++- package.json | 13 +- src/index.js | 122 +++++++---- src/utils.js | 305 ++++++++++++++++++++++----- test/helpers/getCodeFromBundle.js | 6 + test/helpers/getCodeFromStylus.js | 21 ++ test/helpers/normalizeErrors.js | 4 + test/helpers/readAssets.js | 5 + test/helpers/testLoader.cjs | 5 + test/loader.test.js | 15 ++ tsconfig.json | 26 +++ types/index.d.ts | 22 ++ types/utils.d.ts | 249 ++++++++++++++++++++++ 16 files changed, 741 insertions(+), 104 deletions(-) create mode 100644 .changeset/add-jsdoc-types.md create mode 100644 tsconfig.json create mode 100644 types/index.d.ts create mode 100644 types/utils.d.ts diff --git a/.changeset/add-jsdoc-types.md b/.changeset/add-jsdoc-types.md new file mode 100644 index 00000000..a76cc81c --- /dev/null +++ b/.changeset/add-jsdoc-types.md @@ -0,0 +1,5 @@ +--- +"stylus-loader": minor +--- + +Added JSDoc type annotations and TypeScript declaration file generation. The package now ships `types/index.d.ts` and exposes types via the package's `exports`/`types` fields. diff --git a/bench/fixtures/imports/testLoader.js b/bench/fixtures/imports/testLoader.js index 6fc0a1e2..7a374c01 100644 --- a/bench/fixtures/imports/testLoader.js +++ b/bench/fixtures/imports/testLoader.js @@ -1,3 +1,7 @@ +/** + * @param {string} content content + * @returns {string} test loader output + */ function testLoader(content) { return `export default ${JSON.stringify(content)}`; } diff --git a/bench/index.js b/bench/index.js index d917fe5d..5894f4d0 100644 --- a/bench/index.js +++ b/bench/index.js @@ -11,6 +11,10 @@ import importWebpackConfig from "./fixtures/imports/webpack.config.js"; const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +/** + * @param {(...args: unknown[]) => { on: (event: string, callback: () => void) => void }} fn function returning an emitter that emits "complete" + * @returns {(...args: unknown[]) => Promise} a promise-returning function that resolves when "complete" fires + */ function resolveOnComplete(fn) { return (...args) => { const _this = this; diff --git a/package-lock.json b/package-lock.json index 3ce4ba9c..83ca8b1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "8.1.3", "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", "tinyglobby": "^0.2.12" }, "devDependencies": { @@ -18,6 +17,9 @@ "@babel/preset-env": "^7.24.7", "@changesets/cli": "^2.30.0", "@changesets/get-github-info": "^0.8.0", + "@types/node": "^22.19.19", + "@types/normalize-path": "^3.0.2", + "@types/stylus": "^0.48.43", "benchmark": "^2.1.4", "bootstrap-styl": "^5.0.9", "cspell": "^10.0.0", @@ -30,11 +32,13 @@ "lint-staged": "^17.0.5", "memfs": "^4.9.3", "nib": "^1.1.2", + "normalize-path": "^3.0.0", "npm-run-all": "^4.1.5", "prettier": "^3.3.2", "raw-loader": "^4.0.2", "style-loader": "^4.0.0", "stylus": "^0.64.0", + "typescript": "^6.0.3", "webpack": "^5.101.0" }, "engines": { @@ -3784,13 +3788,30 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", - "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/normalize-path": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/normalize-path/-/normalize-path-3.0.2.tgz", + "integrity": "sha512-DO++toKYPaFn0Z8hQ7Tx+3iT9t77IJo/nDiqTXilgEP+kPNIYdpS9kh3fXuc53ugqwp9pxC1PVjCpV1tQDyqMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stylus": { + "version": "0.48.43", + "resolved": "https://registry.npmjs.org/@types/stylus/-/stylus-0.48.43.tgz", + "integrity": "sha512-72dv/zdhuyXWVHUXG2VTPEQdOG+oen95/DNFx2aMFFaY6LoITI6PwEqf5x31JF49kp2w9hvUzkNfTGBIeg61LQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "@types/node": "*" } }, "node_modules/@types/unist": { @@ -10259,6 +10280,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12843,7 +12865,6 @@ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12896,9 +12917,9 @@ } }, "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 6cc22e63..690840aa 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "type": "module", "exports": { ".": { + "types": "./types/index.d.ts", "import": "./dist/esm/index.js", "require": "./dist/cjs/index.js", "default": "./dist/esm/index.js" @@ -27,19 +28,23 @@ }, "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", + "types": "./types/index.d.ts", "files": [ - "dist" + "dist", + "types" ], "scripts": { - "clean": "del-cli dist", + "clean": "del-cli dist types", "prebuild": "npm run clean", "build:esm": "babel src -d dist/esm --env-name esm --copy-files --no-copy-ignored", "build:cjs": "babel src -d dist/cjs --env-name cjs --copy-files --no-copy-ignored && node -e \"const fs=require('fs');fs.writeFileSync('dist/cjs/package.json','{\\\"type\\\":\\\"commonjs\\\"}\\n');fs.appendFileSync('dist/cjs/index.js','module.exports = exports.default;\\nmodule.exports.default = exports.default;\\n')\"", + "build:types": "tsc && prettier \"types/**/*.ts\" --write", "build": "npm-run-all -p \"build:*\"", "security": "npm audit --production", "lint": "npm-run-all -l -p \"lint:**\" && npm run fmt:check", "lint:code": "eslint --cache .", "lint:spelling": "cspell --cache --no-must-find-files --quiet \"**/*.*\"", + "lint:types": "tsc --pretty --noEmit", "fmt": "npm run fmt:check -- --write", "fmt:check": "prettier --list-different --cache --ignore-unknown .", "fix": "npm run fix:code && npm run fmt", @@ -62,6 +67,9 @@ "@babel/preset-env": "^7.24.7", "@changesets/cli": "^2.30.0", "@changesets/get-github-info": "^0.8.0", + "@types/node": "^22.19.19", + "@types/normalize-path": "^3.0.2", + "@types/stylus": "^0.48.43", "benchmark": "^2.1.4", "bootstrap-styl": "^5.0.9", "cspell": "^10.0.0", @@ -79,6 +87,7 @@ "raw-loader": "^4.0.2", "style-loader": "^4.0.0", "stylus": "^0.64.0", + "typescript": "^6.0.3", "webpack": "^5.101.0" }, "peerDependencies": { diff --git a/src/index.js b/src/index.js index 3d418934..1be978af 100644 --- a/src/index.js +++ b/src/index.js @@ -10,8 +10,21 @@ import { urlResolver, } from "./utils.js"; +/** @typedef {import("webpack").LoaderContext} LoaderContext */ +/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */ +/** @typedef {import("./utils.js").LoaderOptions} LoaderOptions */ +/** @typedef {import("./utils.js").StylusError} StylusError */ +/** @typedef {import("./utils.js").StylusOptions} StylusOptions */ +/** @typedef {import("./utils.js").EXPECTED_ANY} EXPECTED_ANY */ + +/** + * The stylus-loader makes `Stylus` available to webpack modules. + * @this {LoaderContext} + * @param {string} source source + * @returns {Promise} loader result + */ export default async function stylusLoader(source) { - const options = this.getOptions(schema); + const options = this.getOptions(/** @type {Schema} */ (schema)); const callback = this.async(); let implementation; @@ -22,7 +35,7 @@ export default async function stylusLoader(source) { options.implementation, ); } catch (error) { - callback(error); + callback(/** @type {Error} */ (error)); return; } @@ -50,12 +63,14 @@ export default async function stylusLoader(source) { try { stylusOptions = await getStylusOptions(this, options); - } catch (err) { - callback(err); + } catch (error) { + callback(/** @type {Error} */ (error)); return; } - const styl = implementation(data, stylusOptions); + const styl = /** @type {import("./utils.js").EXPECTED_ANY} */ ( + implementation(data, stylusOptions) + ); // include regular CSS on @import if (stylusOptions.includeCSS) { @@ -103,7 +118,10 @@ export default async function stylusLoader(source) { } if (stylusOptions.resolveURL !== false) { - styl.define("url", urlResolver(stylusOptions.resolveURL)); + styl.define( + "url", + urlResolver(/** @type {EXPECTED_ANY} */ (stylusOptions.resolveURL)), + ); } const shouldUseWebpackImporter = @@ -121,59 +139,73 @@ export default async function stylusLoader(source) { : Object.entries(stylusOptions.define); for (const defined of definitions) { - styl.define(...defined); + styl.define(.../** @type {[string, EXPECTED_ANY]} */ (defined)); } } - styl.render(async (error, css) => { - if (error) { - if (error.filename) { - this.addDependency(path.normalize(error.filename)); - } + styl.render( + /** + * @param {StylusError | null} error error + * @param {string} css css + * @returns {Promise} render result + */ + async (error, css) => { + if (error) { + if (error.filename) { + this.addDependency(path.normalize(error.filename)); + } - const obj = new Error(error.message, { cause: error }); + const obj = new Error(error.message, { cause: error }); - obj.stack = null; + obj.stack = /** @type {EXPECTED_ANY} */ (null); - callback(obj); + callback(obj); - return; - } + return; + } - if (stylusOptions._imports.length > 0) { - for (const importData of stylusOptions._imports) { - if (path.isAbsolute(importData.path)) { - this.addDependency( - path.normalize( - path.sep === "\\" - ? importData.path.replace(/^\/\/\?\//, "") - : importData.path, - ), - ); - } else { - this.addDependency(path.resolve(process.cwd(), importData.path)); + if (stylusOptions._imports && stylusOptions._imports.length > 0) { + for (const importData of stylusOptions._imports) { + if (path.isAbsolute(importData.path)) { + this.addDependency( + path.normalize( + path.sep === "\\" + ? importData.path.replace(/^\/\/\?\//, "") + : importData.path, + ), + ); + } else { + this.addDependency(path.resolve(process.cwd(), importData.path)); + } } } - } - - let map = styl.sourcemap; - if (map && useSourceMap) { - map = normalizeSourceMap(map, stylusOptions.dest); + let map = styl.sourcemap; - try { - map.sourcesContent = await Promise.all( - map.sources.map(async (file) => - (await readFile(this.fs, file)).toString(), - ), + if (map && useSourceMap) { + map = normalizeSourceMap( + map, + /** @type {string} */ (stylusOptions.dest), ); - } catch (err) { - callback(err); - return; + try { + map.sourcesContent = await Promise.all( + map.sources.map( + /** + * @param {string} file file + * @returns {Promise} file contents + */ + async (file) => (await readFile(this.fs, file)).toString(), + ), + ); + } catch (err) { + callback(/** @type {Error} */ (err)); + + return; + } } - } - callback(null, css, map); - }); + callback(null, css, map); + }, + ); } diff --git a/src/utils.js b/src/utils.js index 5acd1687..2fee5e4e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -3,16 +3,77 @@ import path from "node:path"; import { parse, pathToFileURL } from "node:url"; import normalizePath from "normalize-path"; -import { Compiler, Evaluator, Parser, nodes, utils } from "stylus"; +import stylus from "stylus"; +// @ts-expect-error -- no types are shipped for this internal entry point import DepsResolver from "stylus/lib/visitor/deps-resolver.js"; import { escapePath, glob, isDynamicPattern } from "tinyglobby"; +// eslint-disable-next-line jsdoc/reject-any-type +/** @typedef {any} EXPECTED_ANY */ + +const { + Compiler: StylusCompiler, + Evaluator: StylusEvaluator, + Parser: StylusParser, + nodes, + utils, +} = stylus; +const Compiler = /** @type {EXPECTED_ANY} */ (StylusCompiler); +const Evaluator = /** @type {EXPECTED_ANY} */ (StylusEvaluator); +const Parser = /** @type {EXPECTED_ANY} */ (StylusParser); + +/** @typedef {import("webpack").LoaderContext} LoaderContext */ +/** @typedef {import("stylus/lib/renderer.js")} Renderer */ + +/** + * @typedef {object} StylusOptions + * @property {string=} filename filename + * @property {string=} dest destination + * @property {EXPECTED_ANY[] | EXPECTED_ANY=} use stylus plugins + * @property {string[]=} import files to import + * @property {string[]=} include include paths + * @property {string[]=} imports files to import + * @property {string[]=} paths search paths + * @property {boolean=} compress true if compressed output, otherwise false + * @property {boolean=} includeCSS true if include CSS on `@import`, otherwise false + * @property {boolean=} hoistAtrules true if hoist at-rules, otherwise false + * @property {boolean=} lineNumbers true if line numbers are emitted, otherwise false + * @property {boolean=} disableCache true if cache is disabled, otherwise false + * @property {boolean | EXPECTED_ANY=} sourcemap source map + * @property {boolean | EXPECTED_ANY=} resolveURL `resolveURL` options + * @property {(Record | ([string, EXPECTED_ANY] | [string, EXPECTED_ANY, boolean])[])=} define list of definitions + * @property {{ path: string }[]=} _imports list of imports + */ + +/** + * @typedef {object} LoaderOptions + * @property {(string | ((source: string, options: StylusOptions) => Renderer))=} implementation stylus implementation + * @property {(StylusOptions | ((loaderContext: LoaderContext) => StylusOptions))=} stylusOptions stylus options + * @property {boolean=} sourceMap true if source map is enabled, otherwise false + * @property {boolean=} webpackImporter true if webpack importer is enabled, otherwise false + * @property {(string | ((content: string, loaderContext: LoaderContext) => string | Promise))=} additionalData prepends/appends `Stylus` code to the actual entry file + */ + +/** @typedef {Error & { filename?: string }} StylusError */ + +/** + * @typedef {object} RawSourceMap + * @property {number} version version + * @property {string[]} sources sources + * @property {string[]} names names + * @property {string=} sourceRoot source root + * @property {string[]=} sourcesContent sources content + * @property {string} mappings mappings + * @property {string=} file file + */ + +/** @typedef {(context: string, request: string) => Promise} Resolver */ + /** * Extracts the non-glob base directory from a glob pattern. * Replaces fast-glob's generateTasks()[0].base. - * - * @param {string} pattern - A glob pattern - * @returns {string} The static base path, or "." if there is none + * @param {string} pattern a glob pattern + * @returns {string} the static base path, or "." if there is none */ function getGlobBase(pattern) { const parts = pattern.split("/"); @@ -40,15 +101,27 @@ const IS_MODULE_IMPORT = /^~([^/]+|[^/]+\/|@[^/]+[/][^/]+|@[^/]+\/?|@[^/]+[/][^/]+\/)$/; const MODULE_REQUEST_REGEX = /^[^?]*~/; +/** + * @param {LoaderContext} loaderContext loader context + * @returns {boolean} true when mode is production, otherwise false + */ function isProductionLikeMode(loaderContext) { return loaderContext.mode === "production" || !loaderContext.mode; } +/** + * Derives the stylus options from the loader context and normalizes its values with sane defaults. + * @param {LoaderContext} loaderContext loader context + * @param {LoaderOptions} loaderOptions loader options + * @returns {Promise} stylus options + */ async function getStylusOptions(loaderContext, loaderOptions) { + /** @type {StylusOptions} */ const options = typeof loaderOptions.stylusOptions === "function" ? loaderOptions.stylusOptions(loaderContext) || {} : loaderOptions.stylusOptions || {}; + /** @type {StylusOptions} */ const stylusOptions = { filename: loaderContext.resourcePath, dest: path.dirname(loaderContext.resourcePath), @@ -116,6 +189,12 @@ async function getStylusOptions(loaderContext, loaderOptions) { return stylusOptions; } +/** + * This function is not Webpack-specific and can be used by tools wishing to mimic `stylus-loader`'s behaviour, so its signature should not be changed. + * @param {LoaderContext} loaderContext loader context + * @param {LoaderOptions["implementation"]} implementation stylus implementation + * @returns {Promise<(source: string, options: StylusOptions) => Renderer>} resolved stylus implementation + */ async function getStylusImplementation(loaderContext, implementation) { if (!implementation || typeof implementation === "string") { const specifier = implementation || "stylus"; @@ -129,6 +208,11 @@ async function getStylusImplementation(loaderContext, implementation) { return implementation; } +/** + * @param {LoaderContext} loaderContext loader context + * @param {string} filename filename + * @returns {string[]} possible requests + */ function getPossibleRequests(loaderContext, filename) { let request = filename; @@ -144,6 +228,12 @@ function getPossibleRequests(loaderContext, filename) { return [...new Set([request, filename])]; } +/** + * @param {string} context context + * @param {string[]} possibleRequests possible requests + * @param {Resolver} resolve resolver + * @returns {Promise} resolved request + */ async function resolveRequests(context, possibleRequests, resolve) { if (possibleRequests.length === 0) { throw new Error("Not found"); @@ -166,6 +256,15 @@ async function resolveRequests(context, possibleRequests, resolve) { return result; } +/** + * @param {LoaderContext} loaderContext loader context + * @param {Resolver} fileResolver file resolver + * @param {Resolver} globResolver glob resolver + * @param {boolean} isGlob true when filename is a glob pattern, otherwise false + * @param {string} context context + * @param {string} filename filename + * @returns {Promise} resolved filename or list of files (when glob) + */ async function resolveFilename( loaderContext, fileResolver, @@ -220,6 +319,11 @@ async function resolveFilename( return result; } +/** + * @param {LoaderContext["fs"]} inputFileSystem input file system + * @param {string} filepath file path + * @returns {Promise} file contents + */ function readFile(inputFileSystem, filepath) { return new Promise((resolve, reject) => { inputFileSystem.readFile(filepath, (error, stats) => { @@ -227,13 +331,33 @@ function readFile(inputFileSystem, filepath) { reject(error); } - resolve(stats); + resolve(/** @type {Buffer} */ (stats)); }); }); } const URL_RE = /^(?:url\s*\(\s*)?['"]?(?:[#/]|(?:https?:)?\/\/)/i; +/** + * @typedef {object} Dependency + * @property {number} originalLineno original line number + * @property {number} originalColumn original column + * @property {string} originalNodePath original node path + * @property {string | string[] | Promise} resolved resolved path(s) + * @property {Error=} error resolve error, when failed + */ + +/** + * @param {Map} resolvedDependencies resolved dependencies + * @param {LoaderContext} loaderContext loader context + * @param {Resolver} fileResolver file resolver + * @param {Resolver} globResolver glob resolver + * @param {Set} seen seen files + * @param {string} code code + * @param {string} filename filename + * @param {StylusOptions} options stylus options + * @returns {Promise} resolves once dependencies have been collected + */ async function getDependencies( resolvedDependencies, loaderContext, @@ -248,21 +372,27 @@ async function getDependencies( // See https://github.com/stylus/stylus/issues/2108 const newOptions = { ...options, filename, cache: false }; - const parser = new Parser(code, newOptions); + const parser = /** @type {EXPECTED_ANY} */ (new Parser(code, newOptions)); + /** @type {EXPECTED_ANY} */ let ast; try { ast = parser.parse(); } catch (error) { - loaderContext.emitError(error); + loaderContext.emitError(/** @type {Error} */ (error)); return; } + /** @type {(Dependency & { resolved: string | string[] | Promise })[]} */ const dependencies = []; class ImportVisitor extends DepsResolver { + /** + * @param {EXPECTED_ANY} node import node + * @returns {void} + */ visitImport(node) { let firstNode = node.path.first; @@ -271,7 +401,7 @@ async function getDependencies( } if (!firstNode.val) { - const evaluator = new Evaluator(ast); + const evaluator = /** @type {EXPECTED_ANY} */ (new Evaluator(ast)); firstNode = evaluator.visit(firstNode).first; } @@ -296,17 +426,26 @@ async function getDependencies( const isGlob = isDynamicPattern(nodePath); - let { filename, paths } = this; + const self = /** @type {EXPECTED_ANY} */ (this); + let { filename, paths } = self; if (path.sep === "\\") { filename = filename.replace(/^\\\\\?\\/, ""); - paths = paths.map((item) => item.replace(/^\\\\\?\\/, "")); + paths = paths.map((/** @type {string} */ item) => + item.replace(/^\\\\\?\\/, ""), + ); } - found = utils.find(nodePath, paths, filename); + found = /** @type {EXPECTED_ANY} */ (utils).find( + nodePath, + paths, + filename, + ); if (found && path.sep === "\\") { - found = found.map((item) => item.replace(/^\/\/\?\//, "")); + found = found.map((/** @type {string} */ item) => + item.replace(/^\/\/\?\//, ""), + ); } if (found && isGlob) { @@ -323,7 +462,9 @@ async function getDependencies( found = utils.lookupIndex(oldNodePath, paths, filename); if (found && path.sep === "\\") { - found = found.map((item) => item.replace(/^\/\/\?\//, "")); + found = found.map((/** @type {string} */ item) => + item.replace(/^\/\/\?\//, ""), + ); } } @@ -332,7 +473,7 @@ async function getDependencies( originalLineno: firstNode.lineno, originalColumn: firstNode.column, originalNodePath, - resolved: found.map((item) => + resolved: found.map((/** @type {string} */ item) => path.isAbsolute(item) ? item : path.join(process.cwd(), item), ), }); @@ -356,7 +497,11 @@ async function getDependencies( } } - new ImportVisitor(ast, newOptions).visit(ast); + const visitor = /** @type {EXPECTED_ANY} */ ( + new /** @type {EXPECTED_ANY} */ (ImportVisitor)(ast, newOptions) + ); + + visitor.visit(ast); await Promise.all( [...dependencies].map(async (result) => { @@ -365,9 +510,12 @@ async function getDependencies( try { resolved = await resolved; } catch (err) { - delete result.resolved; + /** @type {EXPECTED_ANY} */ + const r = result; + + delete r.resolved; - result.error = err; + result.error = /** @type {Error} */ (err); return; } @@ -377,12 +525,16 @@ async function getDependencies( // `stylus` returns forward slashes on windows result.resolved = isArray - ? resolved.map((item) => path.normalize(item)) - : path.normalize(resolved); + ? /** @type {string[]} */ (resolved).map((item) => path.normalize(item)) + : path.normalize(/** @type {string} */ (resolved)); const dependenciesOfDependencies = []; - for (const dependency of isArray ? result.resolved : [result.resolved]) { + const items = /** @type {string[]} */ ( + isArray ? result.resolved : [result.resolved] + ); + + for (const dependency of items) { // Avoid loop, the file is imported by itself if (seen.has(dependency)) { return; @@ -404,7 +556,7 @@ async function getDependencies( await readFile(loaderContext.fs, dependency) ).toString(); } catch (error) { - loaderContext.emitError(error); + loaderContext.emitError(/** @type {Error} */ (error)); } await getDependencies( @@ -413,7 +565,7 @@ async function getDependencies( fileResolver, globResolver, seen, - dependencyCode, + /** @type {string} */ (dependencyCode), dependency, options, ); @@ -426,10 +578,17 @@ async function getDependencies( ); if (dependencies.length > 0) { - resolvedDependencies.set(filename, dependencies); + resolvedDependencies.set( + filename, + /** @type {Dependency[]} */ (/** @type {unknown} */ (dependencies)), + ); } } +/** + * @param {EXPECTED_ANY[]} blocks blocks + * @returns {EXPECTED_ANY} merged block + */ function mergeBlocks(blocks) { let finalBlock; @@ -446,6 +605,12 @@ function mergeBlocks(blocks) { return finalBlock; } +/** + * @param {LoaderContext} loaderContext loader context + * @param {string} code code + * @param {StylusOptions} options stylus options + * @returns {Promise} custom evaluator class + */ async function createEvaluator(loaderContext, code, options) { const fileResolve = loaderContext.getResolve({ dependencyType: "stylus", @@ -465,8 +630,11 @@ async function createEvaluator(loaderContext, code, options) { preferRelative: true, }); + /** @type {Map} */ const resolvedImportDependencies = new Map(); + /** @type {Map} */ const resolvedDependencies = new Map(); + /** @type {Set} */ const seen = new Set(); await getDependencies( @@ -480,9 +648,10 @@ async function createEvaluator(loaderContext, code, options) { options, ); + /** @type {{ importPath: string, resolved: string | string[] | Promise, error?: Error }[]} */ const optionsImports = []; - for (const importPath of options.imports) { + for (const importPath of options.imports || []) { const isGlob = isDynamicPattern(importPath); optionsImports.push({ @@ -514,14 +683,21 @@ async function createEvaluator(loaderContext, code, options) { // `stylus` returns forward slashes on windows result.resolved = isArray - ? resolved.map((item) => path.normalize(item)) - : path.normalize(resolved); + ? /** @type {string[]} */ (resolved).map((item) => path.normalize(item)) + : path.normalize(/** @type {string} */ (resolved)); - resolvedImportDependencies.set(importPath, result); + resolvedImportDependencies.set( + importPath, + /** @type {Dependency} */ (/** @type {unknown} */ (result)), + ); const dependenciesOfImportDependencies = []; - for (const dependency of isArray ? result.resolved : [result.resolved]) { + const items = /** @type {string[]} */ ( + isArray ? result.resolved : [result.resolved] + ); + + for (const dependency of items) { dependenciesOfImportDependencies.push( (async () => { let dependencyCode; @@ -531,7 +707,7 @@ async function createEvaluator(loaderContext, code, options) { await readFile(loaderContext.fs, dependency) ).toString(); } catch (error) { - loaderContext.emitError(error); + loaderContext.emitError(/** @type {Error} */ (error)); } await getDependencies( @@ -540,7 +716,7 @@ async function createEvaluator(loaderContext, code, options) { fileResolve, globResolve, seen, - dependencyCode, + /** @type {string} */ (dependencyCode), dependency, options, ); @@ -553,17 +729,25 @@ async function createEvaluator(loaderContext, code, options) { ); return class CustomEvaluator extends Evaluator { + /** + * @param {EXPECTED_ANY} imported imported + * @returns {EXPECTED_ANY} visit result + */ visitImport(imported) { - this.return += 1; + const self = /** @type {EXPECTED_ANY} */ (this); + + self.return += 1; - const node = this.visit(imported.path).first; + const node = self.visit(imported.path).first; const nodePath = (!node.val.isNull && node.val) || node.name; - this.return -= 1; + self.return -= 1; + /** @type {Error & { details?: string, missing?: string[] } | undefined} */ let webpackResolveError; if (node.name !== "url" && nodePath && !URL_RE.test(nodePath)) { + /** @type {Dependency | undefined} */ let dependency; let { filename } = node; @@ -593,7 +777,7 @@ async function createEvaluator(loaderContext, code, options) { if (item.error) { webpackResolveError = item.error; } else { - return item.resolved; + return Boolean(item.resolved); } } @@ -607,13 +791,13 @@ async function createEvaluator(loaderContext, code, options) { if (!Array.isArray(resolved)) { // Avoid re globbing when resolved import contains glob characters - node.string = escapePath(resolved); + node.string = escapePath(/** @type {string} */ (resolved)); } else if (resolved.length > 0) { let hasError = false; const blocks = resolved.map((item) => { const clonedImported = imported.clone(); - const clonedNode = this.visit(clonedImported.path).first; + const clonedNode = self.visit(clonedImported.path).first; // Avoid re globbing when resolved import contains glob characters clonedNode.string = escapePath(item); @@ -621,7 +805,9 @@ async function createEvaluator(loaderContext, code, options) { let result; try { - result = super.visitImport(clonedImported); + result = /** @type {EXPECTED_ANY} */ (super.visitImport)( + clonedImported, + ); } catch { hasError = true; } @@ -639,11 +825,11 @@ async function createEvaluator(loaderContext, code, options) { let result; try { - result = super.visitImport(imported); + result = /** @type {EXPECTED_ANY} */ (super.visitImport)(imported); } catch (error) { loaderContext.emitError( new Error( - `Stylus resolver error: ${error.message}${ + `Stylus resolver error: ${/** @type {Error} */ (error).message}${ webpackResolveError ? `\n\nWebpack resolver error: ${webpackResolveError.message}${ webpackResolveError.details @@ -669,9 +855,21 @@ async function createEvaluator(loaderContext, code, options) { }; } +/** + * @param {{ nocheck?: boolean, paths?: string[] } | boolean=} options url resolver options + * @returns {EXPECTED_ANY} url resolver function + */ function urlResolver(options = {}) { + /** @type {{ nocheck?: boolean, paths?: string[] }} */ + const resolverOptions = typeof options === "boolean" ? {} : options; + + /** + * @this {EXPECTED_ANY} + * @param {EXPECTED_ANY} url url node + * @returns {EXPECTED_ANY} a Literal node + */ function resolver(url) { - const compiler = new Compiler(url); + const compiler = /** @type {EXPECTED_ANY} */ (new Compiler(url)); let { filename } = url; if (path.sep === "\\") { @@ -680,7 +878,9 @@ function urlResolver(options = {}) { compiler.isURL = true; - const visitedUrl = url.nodes.map((node) => compiler.visit(node)).join(""); + const visitedUrl = url.nodes + .map((/** @type {EXPECTED_ANY} */ node) => compiler.visit(node)) + .join(""); const splitted = visitedUrl.split("!"); const parsedUrl = parse(splitted.pop()); @@ -699,19 +899,19 @@ function urlResolver(options = {}) { } // Check that file exists - if (!options.nocheck) { - const _paths = options.paths || []; + if (!resolverOptions.nocheck) { + const _paths = resolverOptions.paths || []; - pathname = utils.lookup(pathname, [ + pathname = /** @type {EXPECTED_ANY} */ (utils).lookup(pathname, [ ..._paths, ...(path.sep === "\\" - ? this.paths.map((item) => + ? this.paths.map((/** @type {string} */ item) => path.normalize(item.replace(/^\/\/\?\//, "")), ) : this.paths), ]); - if (path.sep === "\\") { + if (path.sep === "\\" && pathname) { pathname = pathname.replace(/^\\\\\?\\/, ""); } @@ -739,7 +939,7 @@ function urlResolver(options = {}) { res = path.relative( dest || path.dirname(this.filename), - options.nocheck + resolverOptions.nocheck ? path.join(path.dirname(filename), pathname) : pathname, ) + tail; @@ -753,7 +953,7 @@ function urlResolver(options = {}) { return new nodes.Literal(`url("${splitted.join("!")}")`); } - resolver.options = options; + resolver.options = resolverOptions; resolver.raw = true; return resolver; @@ -762,6 +962,10 @@ function urlResolver(options = {}) { const IS_NATIVE_WIN32_PATH = /^[a-z]:[/\\]|^\\\\/i; const ABSOLUTE_SCHEME = /^[A-Za-z0-9+\-.]+:/; +/** + * @param {string} source source + * @returns {"absolute" | "scheme-relative" | "path-absolute" | "path-relative"} a type of URL + */ function getURLType(source) { if (source[0] === "/") { if (source[1] === "/") { @@ -778,6 +982,11 @@ function getURLType(source) { return ABSOLUTE_SCHEME.test(source) ? "absolute" : "path-relative"; } +/** + * @param {RawSourceMap} map source map + * @param {string} rootContext root context + * @returns {RawSourceMap} normalized source map + */ function normalizeSourceMap(map, rootContext) { const newMap = map; diff --git a/test/helpers/getCodeFromBundle.js b/test/helpers/getCodeFromBundle.js index b63d4c26..66457837 100644 --- a/test/helpers/getCodeFromBundle.js +++ b/test/helpers/getCodeFromBundle.js @@ -2,6 +2,12 @@ import vm from "node:vm"; import readAsset from "./readAsset.js"; +/** + * @param {import("webpack").Stats} stats stats + * @param {import("webpack").Compiler} compiler compiler + * @param {string=} asset asset name + * @returns {unknown} code from bundle + */ function getCodeFromBundle(stats, compiler, asset) { let code = null; diff --git a/test/helpers/getCodeFromStylus.js b/test/helpers/getCodeFromStylus.js index 4e8d89f1..73b55361 100644 --- a/test/helpers/getCodeFromStylus.js +++ b/test/helpers/getCodeFromStylus.js @@ -5,6 +5,10 @@ import { fileURLToPath } from "node:url"; import stylus from "stylus"; import Evaluator from "stylus/lib/visitor/evaluator.js"; +// eslint-disable-next-line jsdoc/reject-any-type +/** @typedef {any} EXPECTED_ANY */ +/** @typedef {EXPECTED_ANY} EvaluatorCtor */ + const __dirname = path.dirname(fileURLToPath(import.meta.url)); const fixturesDir = path.resolve(__dirname, "..", "fixtures"); @@ -85,8 +89,15 @@ const pathMap = { ), }; +/** + * @returns {EvaluatorCtor} a custom evaluator class + */ function evaluator() { return class CustomEvaluator extends Evaluator { + /** + * @param {EXPECTED_ANY} imported imported + * @returns {EXPECTED_ANY} visit result + */ visitImport(imported) { try { return super.visitImport(imported); @@ -117,6 +128,10 @@ function evaluator() { }; } +/** + * @param {EXPECTED_ANY} styl stylus renderer + * @returns {Promise<{ css: string, map: EXPECTED_ANY }>} stylus result + */ function stylRender(styl) { return new Promise((resolve, reject) => { styl.render(async (error, css) => { @@ -131,6 +146,12 @@ function stylRender(styl) { }); } +/** + * @param {string} testId test fixture identifier + * @param {EXPECTED_ANY=} options options + * @param {EXPECTED_ANY=} context test context + * @returns {Promise<{ css: string, map: EXPECTED_ANY }>} stylus result + */ async function getCodeFromStylus(testId, options = {}, context = {}) { const defaultOptions = { shouldUseWebpackImporter: true, diff --git a/test/helpers/normalizeErrors.js b/test/helpers/normalizeErrors.js index 55316385..1df51417 100644 --- a/test/helpers/normalizeErrors.js +++ b/test/helpers/normalizeErrors.js @@ -1,3 +1,7 @@ +/** + * @param {string} str string to normalize + * @returns {string} normalized string with `cwd` removed + */ function removeCWD(str) { const isWin = process.platform === "win32"; let cwd = process.cwd(); diff --git a/test/helpers/readAssets.js b/test/helpers/readAssets.js index b98b7a99..5fcdbb1e 100644 --- a/test/helpers/readAssets.js +++ b/test/helpers/readAssets.js @@ -1,5 +1,10 @@ import readAsset from "./readAsset.js"; +/** + * @param {import("webpack").Compiler} compiler compiler + * @param {import("webpack").Stats} stats stats + * @returns {Record} assets + */ export default function readAssets(compiler, stats) { const assets = {}; diff --git a/test/helpers/testLoader.cjs b/test/helpers/testLoader.cjs index 982591e6..707db73a 100644 --- a/test/helpers/testLoader.cjs +++ b/test/helpers/testLoader.cjs @@ -1,5 +1,10 @@ "use strict"; +/** + * @param {string} content content + * @param {object=} sourceMap source map + * @returns {string} test loader output + */ function testLoader(content, sourceMap) { const result = { css: content }; diff --git a/test/loader.test.js b/test/loader.test.js index bb9b7411..56750dce 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -15,6 +15,9 @@ import { validateDependencies, } from "./helpers/index.js"; +// eslint-disable-next-line jsdoc/reject-any-type +/** @typedef {any} EXPECTED_ANY */ + const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -106,6 +109,9 @@ describe("loader", () => { }); it("should work when stylusOptions is function", async (t) => { + /** + * @returns {(style: EXPECTED_ANY) => void} stylus plugin + */ function plugin() { return (style) => { style.define("add", (a, b) => a.operate("+", b)); @@ -548,6 +554,9 @@ describe("loader", () => { }); it('should work "use" option', async (t) => { + /** + * @returns {(style: EXPECTED_ANY) => void} stylus plugin + */ function plugin() { return (style) => { style.define("add", (a, b) => a.operate("+", b)); @@ -575,6 +584,9 @@ describe("loader", () => { }); it('should work "use" option #1', async (t) => { + /** + * @returns {(style: EXPECTED_ANY) => void} stylus plugin + */ function plugin() { return (style) => { style.define("add", (a, b) => a.operate("+", b)); @@ -669,6 +681,9 @@ describe("loader", () => { it("should work with plugin using bootstrap", async (t) => { const bootstrap = require("bootstrap-styl"); + /** + * @returns {(styl: EXPECTED_ANY) => void} stylus plugin + */ function plugin() { return (styl) => { bootstrap()(styl); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..8b7233a0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + + "resolveJsonModule": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "allowImportingTsExtensions": false, + + "types": ["node"], + + "rootDir": "./src", + "outDir": "./types", + + "allowJs": true, + "checkJs": true, + "strict": true, + "skipLibCheck": true, + + "declaration": true, + "emitDeclarationOnly": true + }, + "include": ["./src/**/*"] +} diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..28801539 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,22 @@ +/** @typedef {import("webpack").LoaderContext} LoaderContext */ +/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */ +/** @typedef {import("./utils.js").LoaderOptions} LoaderOptions */ +/** @typedef {import("./utils.js").StylusError} StylusError */ +/** @typedef {import("./utils.js").StylusOptions} StylusOptions */ +/** @typedef {import("./utils.js").EXPECTED_ANY} EXPECTED_ANY */ +/** + * The stylus-loader makes `Stylus` available to webpack modules. + * @this {LoaderContext} + * @param {string} source source + * @returns {Promise} loader result + */ +export default function stylusLoader( + this: import("webpack").LoaderContext, + source: string, +): Promise; +export type LoaderContext = import("webpack").LoaderContext; +export type Schema = import("schema-utils/declarations/validate").Schema; +export type LoaderOptions = import("./utils.js").LoaderOptions; +export type StylusError = import("./utils.js").StylusError; +export type StylusOptions = import("./utils.js").StylusOptions; +export type EXPECTED_ANY = import("./utils.js").EXPECTED_ANY; diff --git a/types/utils.d.ts b/types/utils.d.ts new file mode 100644 index 00000000..fd6b0dc4 --- /dev/null +++ b/types/utils.d.ts @@ -0,0 +1,249 @@ +export type LoaderContext = import("webpack").LoaderContext; +export type Renderer = import("stylus/lib/renderer.js"); +export type StylusOptions = { + /** + * filename + */ + filename?: string | undefined; + /** + * destination + */ + dest?: string | undefined; + /** + * stylus plugins + */ + use?: (EXPECTED_ANY[] | EXPECTED_ANY) | undefined; + /** + * files to import + */ + import?: string[] | undefined; + /** + * include paths + */ + include?: string[] | undefined; + /** + * files to import + */ + imports?: string[] | undefined; + /** + * search paths + */ + paths?: string[] | undefined; + /** + * true if compressed output, otherwise false + */ + compress?: boolean | undefined; + /** + * true if include CSS on `@import`, otherwise false + */ + includeCSS?: boolean | undefined; + /** + * true if hoist at-rules, otherwise false + */ + hoistAtrules?: boolean | undefined; + /** + * true if line numbers are emitted, otherwise false + */ + lineNumbers?: boolean | undefined; + /** + * true if cache is disabled, otherwise false + */ + disableCache?: boolean | undefined; + /** + * source map + */ + sourcemap?: (boolean | EXPECTED_ANY) | undefined; + /** + * `resolveURL` options + */ + resolveURL?: (boolean | EXPECTED_ANY) | undefined; + /** + * list of definitions + */ + define?: + | ( + | Record + | ([string, EXPECTED_ANY] | [string, EXPECTED_ANY, boolean])[] + ) + | undefined; + /** + * list of imports + */ + _imports?: + | { + path: string; + }[] + | undefined; +}; +export type LoaderOptions = { + /** + * stylus implementation + */ + implementation?: + | (string | ((source: string, options: StylusOptions) => Renderer)) + | undefined; + /** + * stylus options + */ + stylusOptions?: + | (StylusOptions | ((loaderContext: LoaderContext) => StylusOptions)) + | undefined; + /** + * true if source map is enabled, otherwise false + */ + sourceMap?: boolean | undefined; + /** + * true if webpack importer is enabled, otherwise false + */ + webpackImporter?: boolean | undefined; + /** + * prepends/appends `Stylus` code to the actual entry file + */ + additionalData?: + | ( + | string + | (( + content: string, + loaderContext: LoaderContext, + ) => string | Promise) + ) + | undefined; +}; +export type StylusError = Error & { + filename?: string; +}; +export type RawSourceMap = { + /** + * version + */ + version: number; + /** + * sources + */ + sources: string[]; + /** + * names + */ + names: string[]; + /** + * source root + */ + sourceRoot?: string | undefined; + /** + * sources content + */ + sourcesContent?: string[] | undefined; + /** + * mappings + */ + mappings: string; + /** + * file + */ + file?: string | undefined; +}; +export type Resolver = (context: string, request: string) => Promise; +export type Dependency = { + /** + * original line number + */ + originalLineno: number; + /** + * original column + */ + originalColumn: number; + /** + * original node path + */ + originalNodePath: string; + /** + * resolved path(s) + */ + resolved: string | string[] | Promise; + /** + * resolve error, when failed + */ + error?: Error | undefined; +}; +export type EXPECTED_ANY = any; +/** + * @param {LoaderContext} loaderContext loader context + * @param {string} code code + * @param {StylusOptions} options stylus options + * @returns {Promise} custom evaluator class + */ +export function createEvaluator( + loaderContext: LoaderContext, + code: string, + options: StylusOptions, +): Promise; +/** + * This function is not Webpack-specific and can be used by tools wishing to mimic `stylus-loader`'s behaviour, so its signature should not be changed. + * @param {LoaderContext} loaderContext loader context + * @param {LoaderOptions["implementation"]} implementation stylus implementation + * @returns {Promise<(source: string, options: StylusOptions) => Renderer>} resolved stylus implementation + */ +export function getStylusImplementation( + loaderContext: LoaderContext, + implementation: LoaderOptions["implementation"], +): Promise<(source: string, options: StylusOptions) => Renderer>; +/** + * Derives the stylus options from the loader context and normalizes its values with sane defaults. + * @param {LoaderContext} loaderContext loader context + * @param {LoaderOptions} loaderOptions loader options + * @returns {Promise} stylus options + */ +export function getStylusOptions( + loaderContext: LoaderContext, + loaderOptions: LoaderOptions, +): Promise; +/** + * @param {RawSourceMap} map source map + * @param {string} rootContext root context + * @returns {RawSourceMap} normalized source map + */ +export function normalizeSourceMap( + map: RawSourceMap, + rootContext: string, +): RawSourceMap; +/** + * @param {LoaderContext["fs"]} inputFileSystem input file system + * @param {string} filepath file path + * @returns {Promise} file contents + */ +export function readFile( + inputFileSystem: LoaderContext["fs"], + filepath: string, +): Promise; +/** + * @param {LoaderContext} loaderContext loader context + * @param {Resolver} fileResolver file resolver + * @param {Resolver} globResolver glob resolver + * @param {boolean} isGlob true when filename is a glob pattern, otherwise false + * @param {string} context context + * @param {string} filename filename + * @returns {Promise} resolved filename or list of files (when glob) + */ +export function resolveFilename( + loaderContext: LoaderContext, + fileResolver: Resolver, + globResolver: Resolver, + isGlob: boolean, + context: string, + filename: string, +): Promise; +/** + * @param {{ nocheck?: boolean, paths?: string[] } | boolean=} options url resolver options + * @returns {EXPECTED_ANY} url resolver function + */ +export function urlResolver( + options?: + | ( + | { + nocheck?: boolean; + paths?: string[]; + } + | boolean + ) + | undefined, +): EXPECTED_ANY; From f1311d858fdf3606e04abc1bc031337c7c4d35e7 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Mon, 25 May 2026 19:45:41 +0300 Subject: [PATCH 2/5] refactor: types --- package-lock.json | 8 +-- package.json | 2 +- src/index.js | 10 +--- src/utils.js | 136 +++++++++++++++++++++++++++++----------------- types/utils.d.ts | 55 +++++++++++++------ 5 files changed, 130 insertions(+), 81 deletions(-) diff --git a/package-lock.json b/package-lock.json index 83ca8b1f..f0ac6a85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "8.1.3", "license": "MIT", "dependencies": { + "@types/stylus": "^0.48.43", + "normalize-path": "^3.0.0", "tinyglobby": "^0.2.12" }, "devDependencies": { @@ -19,7 +21,6 @@ "@changesets/get-github-info": "^0.8.0", "@types/node": "^22.19.19", "@types/normalize-path": "^3.0.2", - "@types/stylus": "^0.48.43", "benchmark": "^2.1.4", "bootstrap-styl": "^5.0.9", "cspell": "^10.0.0", @@ -32,7 +33,6 @@ "lint-staged": "^17.0.5", "memfs": "^4.9.3", "nib": "^1.1.2", - "normalize-path": "^3.0.0", "npm-run-all": "^4.1.5", "prettier": "^3.3.2", "raw-loader": "^4.0.2", @@ -3791,7 +3791,6 @@ "version": "22.19.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3808,7 +3807,6 @@ "version": "0.48.43", "resolved": "https://registry.npmjs.org/@types/stylus/-/stylus-0.48.43.tgz", "integrity": "sha512-72dv/zdhuyXWVHUXG2VTPEQdOG+oen95/DNFx2aMFFaY6LoITI6PwEqf5x31JF49kp2w9hvUzkNfTGBIeg61LQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -10280,7 +10278,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12920,7 +12917,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index 690840aa..7735967b 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "release": "npm run build && changeset publish" }, "dependencies": { + "@types/stylus": "^0.48.43", "normalize-path": "^3.0.0", "tinyglobby": "^0.2.12" }, @@ -69,7 +70,6 @@ "@changesets/get-github-info": "^0.8.0", "@types/node": "^22.19.19", "@types/normalize-path": "^3.0.2", - "@types/stylus": "^0.48.43", "benchmark": "^2.1.4", "bootstrap-styl": "^5.0.9", "cspell": "^10.0.0", diff --git a/src/index.js b/src/index.js index 1be978af..b7cc86e0 100644 --- a/src/index.js +++ b/src/index.js @@ -68,9 +68,7 @@ export default async function stylusLoader(source) { return; } - const styl = /** @type {import("./utils.js").EXPECTED_ANY} */ ( - implementation(data, stylusOptions) - ); + const styl = implementation(data, stylusOptions); // include regular CSS on @import if (stylusOptions.includeCSS) { @@ -118,10 +116,7 @@ export default async function stylusLoader(source) { } if (stylusOptions.resolveURL !== false) { - styl.define( - "url", - urlResolver(/** @type {EXPECTED_ANY} */ (stylusOptions.resolveURL)), - ); + styl.define("url", urlResolver(stylusOptions.resolveURL)); } const shouldUseWebpackImporter = @@ -180,6 +175,7 @@ export default async function stylusLoader(source) { } } + // @ts-expect-error no types are shipped for this let map = styl.sourcemap; if (map && useSourceMap) { diff --git a/src/utils.js b/src/utils.js index 2fee5e4e..00123f19 100644 --- a/src/utils.js +++ b/src/utils.js @@ -4,7 +4,7 @@ import { parse, pathToFileURL } from "node:url"; import normalizePath from "normalize-path"; import stylus from "stylus"; -// @ts-expect-error -- no types are shipped for this internal entry point +// @ts-expect-error no types are shipped for this internal entry point import DepsResolver from "stylus/lib/visitor/deps-resolver.js"; import { escapePath, glob, isDynamicPattern } from "tinyglobby"; @@ -18,33 +18,54 @@ const { nodes, utils, } = stylus; -const Compiler = /** @type {EXPECTED_ANY} */ (StylusCompiler); -const Evaluator = /** @type {EXPECTED_ANY} */ (StylusEvaluator); -const Parser = /** @type {EXPECTED_ANY} */ (StylusParser); +const Compiler = StylusCompiler; +const Evaluator = StylusEvaluator; +const Parser = StylusParser; /** @typedef {import("webpack").LoaderContext} LoaderContext */ /** @typedef {import("stylus/lib/renderer.js")} Renderer */ +/** @typedef {import("stylus").RenderOptions} RenderOptions */ + +/** + * @typedef {object} StylusResolveUrlOptions + * @property {boolean=} nocheck true when no need to check on disk, otherwise false + * @property {string[]=} paths additional paths + */ + +/** + * @callback StylusPluginFn + * @param {EXPECTED_ANY} renderer render fn + * @returns {void} + */ + /** - * @typedef {object} StylusOptions - * @property {string=} filename filename + * @typedef {object} StylusSourceMapOptions + * @property {boolean=} comment append the source map URL comment to the CSS. + * @property {string=} sourceRoot root URL for the source files. + * @property {string=} basePath base path to resolve relative source map paths. + * @property {boolean=} inline embed the source map directly into the CSS as Base64. + */ + +/** + * @typedef {object} NoTypesStylusOptions * @property {string=} dest destination - * @property {EXPECTED_ANY[] | EXPECTED_ANY=} use stylus plugins + * @property {StylusPluginFn[] | StylusPluginFn=} use stylus plugins * @property {string[]=} import files to import * @property {string[]=} include include paths - * @property {string[]=} imports files to import - * @property {string[]=} paths search paths * @property {boolean=} compress true if compressed output, otherwise false * @property {boolean=} includeCSS true if include CSS on `@import`, otherwise false * @property {boolean=} hoistAtrules true if hoist at-rules, otherwise false * @property {boolean=} lineNumbers true if line numbers are emitted, otherwise false * @property {boolean=} disableCache true if cache is disabled, otherwise false - * @property {boolean | EXPECTED_ANY=} sourcemap source map - * @property {boolean | EXPECTED_ANY=} resolveURL `resolveURL` options + * @property {boolean | StylusSourceMapOptions=} sourcemap source map + * @property {boolean | StylusResolveUrlOptions=} resolveURL `resolveURL` options * @property {(Record | ([string, EXPECTED_ANY] | [string, EXPECTED_ANY, boolean])[])=} define list of definitions * @property {{ path: string }[]=} _imports list of imports */ +/** @typedef {RenderOptions & NoTypesStylusOptions} StylusOptions */ + /** * @typedef {object} LoaderOptions * @property {(string | ((source: string, options: StylusOptions) => Renderer))=} implementation stylus implementation @@ -343,7 +364,7 @@ const URL_RE = /^(?:url\s*\(\s*)?['"]?(?:[#/]|(?:https?:)?\/\/)/i; * @property {number} originalLineno original line number * @property {number} originalColumn original column * @property {string} originalNodePath original node path - * @property {string | string[] | Promise} resolved resolved path(s) + * @property {undefined | string | string[] | Promise} resolved resolved path(s) * @property {Error=} error resolve error, when failed */ @@ -372,12 +393,14 @@ async function getDependencies( // See https://github.com/stylus/stylus/issues/2108 const newOptions = { ...options, filename, cache: false }; - const parser = /** @type {EXPECTED_ANY} */ (new Parser(code, newOptions)); + // @ts-expect-error no types are shipped + const parser = new Parser(code, newOptions); /** @type {EXPECTED_ANY} */ let ast; try { + // @ts-expect-error no types are shipped ast = parser.parse(); } catch (error) { loaderContext.emitError(/** @type {Error} */ (error)); @@ -385,29 +408,38 @@ async function getDependencies( return; } - /** @type {(Dependency & { resolved: string | string[] | Promise })[]} */ + /** @type {(Dependency & { resolved?: undefined | string | string[] | Promise })[]} */ const dependencies = []; class ImportVisitor extends DepsResolver { /** - * @param {EXPECTED_ANY} node import node + * @param {import("stylus").nodes.Import} node import node * @returns {void} */ visitImport(node) { let firstNode = node.path.first; - if (firstNode.name === "url") { + if ( + /** @type {import("stylus").nodes.Call} */ + (firstNode).name === "url" + ) { return; } - if (!firstNode.val) { - const evaluator = /** @type {EXPECTED_ANY} */ (new Evaluator(ast)); + if (!(/** @type {import("stylus").nodes.String} */ (firstNode).val)) { + // @ts-expect-error no types are shipped for this + const evaluator = new Evaluator(ast); + // @ts-expect-error no types are shipped for this firstNode = evaluator.visit(firstNode).first; } const originalNodePath = - (!firstNode.val.isNull && firstNode.val) || firstNode.name; + // @ts-expect-error bad types + (!firstNode.val.isNull && + /** @type {import("stylus").nodes.String} */ (firstNode).val) || + /** @type {import("stylus").nodes.Ident} */ + (firstNode).name; let nodePath = originalNodePath; if (!nodePath) { @@ -426,8 +458,8 @@ async function getDependencies( const isGlob = isDynamicPattern(nodePath); - const self = /** @type {EXPECTED_ANY} */ (this); - let { filename, paths } = self; + // @ts-expect-error no types are shipped for this + let { filename, paths } = this; if (path.sep === "\\") { filename = filename.replace(/^\\\\\?\\/, ""); @@ -436,11 +468,7 @@ async function getDependencies( ); } - found = /** @type {EXPECTED_ANY} */ (utils).find( - nodePath, - paths, - filename, - ); + found = utils.find(nodePath, paths, filename); if (found && path.sep === "\\") { found = found.map((/** @type {string} */ item) => @@ -497,10 +525,10 @@ async function getDependencies( } } - const visitor = /** @type {EXPECTED_ANY} */ ( - new /** @type {EXPECTED_ANY} */ (ImportVisitor)(ast, newOptions) - ); + // @ts-expect-error no types are shipped for this + const visitor = new ImportVisitor(ast, newOptions); + // @ts-expect-error no types are shipped for this visitor.visit(ast); await Promise.all( @@ -510,7 +538,6 @@ async function getDependencies( try { resolved = await resolved; } catch (err) { - /** @type {EXPECTED_ANY} */ const r = result; delete r.resolved; @@ -586,10 +613,11 @@ async function getDependencies( } /** - * @param {EXPECTED_ANY[]} blocks blocks - * @returns {EXPECTED_ANY} merged block + * @param {import("stylus").nodes.Block[]} blocks blocks + * @returns {import("stylus").nodes.Block | undefined} merged block */ function mergeBlocks(blocks) { + /** @type {import("stylus").nodes.Block | undefined} */ let finalBlock; for (const block of blocks) { @@ -609,7 +637,7 @@ function mergeBlocks(blocks) { * @param {LoaderContext} loaderContext loader context * @param {string} code code * @param {StylusOptions} options stylus options - * @returns {Promise} custom evaluator class + * @returns {Promise} custom evaluator class */ async function createEvaluator(loaderContext, code, options) { const fileResolve = loaderContext.getResolve({ @@ -730,17 +758,20 @@ async function createEvaluator(loaderContext, code, options) { return class CustomEvaluator extends Evaluator { /** - * @param {EXPECTED_ANY} imported imported - * @returns {EXPECTED_ANY} visit result + * @param {import("stylus").nodes.Import} imported imported + * @returns {import("stylus").nodes.Block | import("stylus").nodes.Import | undefined} visit result */ visitImport(imported) { - const self = /** @type {EXPECTED_ANY} */ (this); + const self = this; + // @ts-expect-error internal logic self.return += 1; + // @ts-expect-error no types are shipped for this const node = self.visit(imported.path).first; const nodePath = (!node.val.isNull && node.val) || node.name; + // @ts-expect-error internal logic self.return -= 1; /** @type {Error & { details?: string, missing?: string[] } | undefined} */ @@ -795,8 +826,10 @@ async function createEvaluator(loaderContext, code, options) { } else if (resolved.length > 0) { let hasError = false; + /** @type {import("stylus").nodes.Block[]} */ const blocks = resolved.map((item) => { const clonedImported = imported.clone(); + // @ts-expect-error no types are shipped for this const clonedNode = self.visit(clonedImported.path).first; // Avoid re globbing when resolved import contains glob characters @@ -805,9 +838,8 @@ async function createEvaluator(loaderContext, code, options) { let result; try { - result = /** @type {EXPECTED_ANY} */ (super.visitImport)( - clonedImported, - ); + // @ts-expect-error no types are shipped for this + result = super.visitImport(clonedImported); } catch { hasError = true; } @@ -822,10 +854,12 @@ async function createEvaluator(loaderContext, code, options) { } } + /** @type {import("stylus").nodes.Block | undefined} */ let result; try { - result = /** @type {EXPECTED_ANY} */ (super.visitImport)(imported); + // @ts-expect-error no types are shipped for this + result = super.visitImport(imported); } catch (error) { loaderContext.emitError( new Error( @@ -864,30 +898,31 @@ function urlResolver(options = {}) { const resolverOptions = typeof options === "boolean" ? {} : options; /** - * @this {EXPECTED_ANY} - * @param {EXPECTED_ANY} url url node - * @returns {EXPECTED_ANY} a Literal node + * @this {import("stylus").Evaluator & { paths: string[], filename: string, includeCSS?: boolean }} + * @param {import("stylus").nodes.Expression} url url node + * @returns {import("stylus").nodes.Literal} a Literal node */ function resolver(url) { - const compiler = /** @type {EXPECTED_ANY} */ (new Compiler(url)); + // @ts-expect-error no types are shipped for this + const compiler = new Compiler(url); let { filename } = url; if (path.sep === "\\") { filename = filename.replace(/^\/\/\?\//, ""); } + // @ts-expect-error no types are shipped for this compiler.isURL = true; - const visitedUrl = url.nodes - .map((/** @type {EXPECTED_ANY} */ node) => compiler.visit(node)) - .join(""); + // @ts-expect-error no types are shipped for this + const visitedUrl = url.nodes.map((node) => compiler.visit(node)).join(""); const splitted = visitedUrl.split("!"); - - const parsedUrl = parse(splitted.pop()); + const parsedUrl = parse(/** @type {string} */ (splitted.pop())); // Parse literal const literal = new nodes.Literal(`url("${parsedUrl.href}")`); let { pathname } = parsedUrl; + // @ts-expect-error no types are shipped for this let { dest } = this.options; let tail = ""; @@ -902,7 +937,8 @@ function urlResolver(options = {}) { if (!resolverOptions.nocheck) { const _paths = resolverOptions.paths || []; - pathname = /** @type {EXPECTED_ANY} */ (utils).lookup(pathname, [ + // @ts-expect-error bad types + pathname = utils.lookup(pathname, [ ..._paths, ...(path.sep === "\\" ? this.paths.map((/** @type {string} */ item) => diff --git a/types/utils.d.ts b/types/utils.d.ts index fd6b0dc4..be6ae953 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -1,10 +1,36 @@ export type LoaderContext = import("webpack").LoaderContext; export type Renderer = import("stylus/lib/renderer.js"); -export type StylusOptions = { +export type RenderOptions = import("stylus").RenderOptions; +export type StylusResolveUrlOptions = { /** - * filename + * true when no need to check on disk, otherwise false */ - filename?: string | undefined; + nocheck?: boolean | undefined; + /** + * additional paths + */ + paths?: string[] | undefined; +}; +export type StylusPluginFn = (renderer: EXPECTED_ANY) => void; +export type StylusSourceMapOptions = { + /** + * append the source map URL comment to the CSS. + */ + comment?: boolean | undefined; + /** + * root URL for the source files. + */ + sourceRoot?: string | undefined; + /** + * base path to resolve relative source map paths. + */ + basePath?: string | undefined; + /** + * embed the source map directly into the CSS as Base64. + */ + inline?: boolean | undefined; +}; +export type NoTypesStylusOptions = { /** * destination */ @@ -12,7 +38,7 @@ export type StylusOptions = { /** * stylus plugins */ - use?: (EXPECTED_ANY[] | EXPECTED_ANY) | undefined; + use?: (StylusPluginFn[] | StylusPluginFn) | undefined; /** * files to import */ @@ -21,14 +47,6 @@ export type StylusOptions = { * include paths */ include?: string[] | undefined; - /** - * files to import - */ - imports?: string[] | undefined; - /** - * search paths - */ - paths?: string[] | undefined; /** * true if compressed output, otherwise false */ @@ -52,11 +70,11 @@ export type StylusOptions = { /** * source map */ - sourcemap?: (boolean | EXPECTED_ANY) | undefined; + sourcemap?: (boolean | StylusSourceMapOptions) | undefined; /** * `resolveURL` options */ - resolveURL?: (boolean | EXPECTED_ANY) | undefined; + resolveURL?: (boolean | StylusResolveUrlOptions) | undefined; /** * list of definitions */ @@ -75,6 +93,7 @@ export type StylusOptions = { }[] | undefined; }; +export type StylusOptions = RenderOptions & NoTypesStylusOptions; export type LoaderOptions = { /** * stylus implementation @@ -159,7 +178,7 @@ export type Dependency = { /** * resolved path(s) */ - resolved: string | string[] | Promise; + resolved: undefined | string | string[] | Promise; /** * resolve error, when failed */ @@ -170,13 +189,15 @@ export type EXPECTED_ANY = any; * @param {LoaderContext} loaderContext loader context * @param {string} code code * @param {StylusOptions} options stylus options - * @returns {Promise} custom evaluator class + * @returns {Promise} custom evaluator class */ export function createEvaluator( loaderContext: LoaderContext, code: string, options: StylusOptions, -): Promise; +): Promise<{ + new (): {}; +}>; /** * This function is not Webpack-specific and can be used by tools wishing to mimic `stylus-loader`'s behaviour, so its signature should not be changed. * @param {LoaderContext} loaderContext loader context From 38cbd67c6dbea0111e05caea58222958ae65619f Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Mon, 25 May 2026 20:13:45 +0300 Subject: [PATCH 3/5] chore: fix lint --- README.md | 24 ++++++------------------ src/utils.js | 5 ++--- types/utils.d.ts | 2 +- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f8812ac8..c9d9707f 100644 --- a/README.md +++ b/README.md @@ -113,18 +113,17 @@ module.exports = { loader: "stylus-loader", options: { stylusOptions: { + // eslint-disable-next-line jsdoc/no-restricted-syntax /** * Specify Stylus plugins to use. Plugins may be passed as * strings instead of importing them in your Webpack config. - * - * @type {(string|Function)[]} + * @type {(string | (renderer: object) => void)[]} * @default [] */ use: ["nib"], /** * Add path(s) to the import lookup paths. - * * @type {string[]} * @default [] */ @@ -132,7 +131,6 @@ module.exports = { /** * Import the specified Stylus files/paths. - * * @type {string[]} * @default [] */ @@ -140,8 +138,7 @@ module.exports = { /** * Define Stylus variables or functions. - * - * @type {Array|Object} + * @type {[string, string | number | boolean, boolean?] | Record} * @default {} */ // Array is the recommended syntax: [key, value, raw] @@ -156,8 +153,7 @@ module.exports = { // }, /** - * Include regular CSS on @import. - * + * Include regular CSS on \@import. * @type {boolean} * @default false */ @@ -165,10 +161,8 @@ module.exports = { /** * Resolve relative url()'s inside imported files. - * * @see https://stylus-lang.com/docs/js.html#stylusresolveroptions - * - * @type {boolean|Object} + * @type {boolean | { nocheck?: boolean }} * @default { nocheck: true } */ resolveURL: true, @@ -176,19 +170,15 @@ module.exports = { /** * Emits comments in the generated CSS indicating the corresponding Stylus line. - * * @see https://stylus-lang.com/docs/executable.html - * * @type {boolean} * @default false */ lineNumbers: true, /** - * Move @import and @charset to the top. - * + * Move \@import and \@charset to the top. * @see https://stylus-lang.com/docs/executable.html - * * @type {boolean} * @default false */ @@ -197,9 +187,7 @@ module.exports = { /** * Compress CSS output. * In the "production" mode is `true` by default - * * @see https://stylus-lang.com/docs/executable.html - * * @type {boolean} * @default false */ diff --git a/src/utils.js b/src/utils.js index 00123f19..2ab7ab99 100644 --- a/src/utils.js +++ b/src/utils.js @@ -30,12 +30,11 @@ const Parser = StylusParser; /** * @typedef {object} StylusResolveUrlOptions * @property {boolean=} nocheck true when no need to check on disk, otherwise false - * @property {string[]=} paths additional paths */ /** * @callback StylusPluginFn - * @param {EXPECTED_ANY} renderer render fn + * @param {object} renderer renderer * @returns {void} */ @@ -50,7 +49,7 @@ const Parser = StylusParser; /** * @typedef {object} NoTypesStylusOptions * @property {string=} dest destination - * @property {StylusPluginFn[] | StylusPluginFn=} use stylus plugins + * @property {string | StylusPluginFn | (string | StylusPluginFn)[]=} use stylus plugins * @property {string[]=} import files to import * @property {string[]=} include include paths * @property {boolean=} compress true if compressed output, otherwise false diff --git a/types/utils.d.ts b/types/utils.d.ts index be6ae953..bfad1e0a 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -38,7 +38,7 @@ export type NoTypesStylusOptions = { /** * stylus plugins */ - use?: (StylusPluginFn[] | StylusPluginFn) | undefined; + use?: (string | StylusPluginFn | (string | StylusPluginFn)[]) | undefined; /** * files to import */ From 3543345f2fab37822bf3a1b35452f0897ea77180 Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Mon, 25 May 2026 20:23:25 +0300 Subject: [PATCH 4/5] chore: refactor --- .github/workflows/nodejs.yml | 6 ++++++ README.md | 2 +- src/utils.js | 1 + types/utils.d.ts | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 81d4266b..ef77de88 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -51,6 +51,12 @@ jobs: - name: Security audit run: npm run security + - name: Build types + run: npm run build:types + + - name: Check types + run: if [ -n "$(git status types --porcelain)" ]; then echo "Missing types. Update types by running 'npm run build:types'"; exit 1; else echo "All types are valid"; fi + test: name: Test - ${{ matrix.os }} - Node v${{ matrix.node-version }}, Webpack ${{ matrix.webpack-version }} diff --git a/README.md b/README.md index c9d9707f..27b5973d 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ module.exports = { /** * Resolve relative url()'s inside imported files. * @see https://stylus-lang.com/docs/js.html#stylusresolveroptions - * @type {boolean | { nocheck?: boolean }} + * @type {boolean | { nocheck?: boolean, paths?: string[] }} * @default { nocheck: true } */ resolveURL: true, diff --git a/src/utils.js b/src/utils.js index 2ab7ab99..9cd5ff2e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -30,6 +30,7 @@ const Parser = StylusParser; /** * @typedef {object} StylusResolveUrlOptions * @property {boolean=} nocheck true when no need to check on disk, otherwise false + * @property {string[]=} paths additional paths */ /** diff --git a/types/utils.d.ts b/types/utils.d.ts index bfad1e0a..63d92789 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -11,7 +11,7 @@ export type StylusResolveUrlOptions = { */ paths?: string[] | undefined; }; -export type StylusPluginFn = (renderer: EXPECTED_ANY) => void; +export type StylusPluginFn = (renderer: object) => void; export type StylusSourceMapOptions = { /** * append the source map URL comment to the CSS. From b2c6a99fb445297a78c0dd180cfd70312026941e Mon Sep 17 00:00:00 2001 From: alexander-akait Date: Mon, 25 May 2026 20:35:15 +0300 Subject: [PATCH 5/5] chore: refactor --- src/utils.js | 2 +- types/utils.d.ts | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/utils.js b/src/utils.js index 9cd5ff2e..1c4bc5f6 100644 --- a/src/utils.js +++ b/src/utils.js @@ -637,7 +637,7 @@ function mergeBlocks(blocks) { * @param {LoaderContext} loaderContext loader context * @param {string} code code * @param {StylusOptions} options stylus options - * @returns {Promise} custom evaluator class + * @returns {Promise} custom evaluator class */ async function createEvaluator(loaderContext, code, options) { const fileResolve = loaderContext.getResolve({ diff --git a/types/utils.d.ts b/types/utils.d.ts index 63d92789..05eb6838 100644 --- a/types/utils.d.ts +++ b/types/utils.d.ts @@ -189,15 +189,13 @@ export type EXPECTED_ANY = any; * @param {LoaderContext} loaderContext loader context * @param {string} code code * @param {StylusOptions} options stylus options - * @returns {Promise} custom evaluator class + * @returns {Promise} custom evaluator class */ export function createEvaluator( loaderContext: LoaderContext, code: string, options: StylusOptions, -): Promise<{ - new (): {}; -}>; +): Promise; /** * This function is not Webpack-specific and can be used by tools wishing to mimic `stylus-loader`'s behaviour, so its signature should not be changed. * @param {LoaderContext} loaderContext loader context @@ -268,3 +266,7 @@ export function urlResolver( ) | undefined, ): EXPECTED_ANY; +declare const StylusEvaluator: { + new (): {}; +}; +export {};