diff options
Diffstat (limited to 'node_modules/doiuse/lib')
| -rw-r--r-- | node_modules/doiuse/lib/BrowserSelection.js | 181 | ||||
| -rw-r--r-- | node_modules/doiuse/lib/Detector.js | 157 | ||||
| -rw-r--r-- | node_modules/doiuse/lib/DoIUse.js | 130 | ||||
| -rw-r--r-- | node_modules/doiuse/lib/stream/CssUsageDuplex.js | 36 | ||||
| -rw-r--r-- | node_modules/doiuse/lib/stream/RuleUsageTransform.js | 80 | ||||
| -rw-r--r-- | node_modules/doiuse/lib/stream/SourceMapTransform.js | 74 |
6 files changed, 0 insertions, 658 deletions
diff --git a/node_modules/doiuse/lib/BrowserSelection.js b/node_modules/doiuse/lib/BrowserSelection.js deleted file mode 100644 index 17467548..00000000 --- a/node_modules/doiuse/lib/BrowserSelection.js +++ /dev/null @@ -1,181 +0,0 @@ -import browserslist from 'browserslist'; -import * as caniuse from 'caniuse-lite'; - -import FEATURES from '../data/features.js'; -import { formatBrowserName } from '../utils/util.js'; - -/** - * @template T - * @typedef {{missing: T, partial: T}} Filter - */ - -/** - * @typedef {Object} MissingSupportResultStats - * @prop {string} title - * @prop {caniuse.Feature} caniuseData - * @prop {string} [missing] - * @prop {Record<string, Record<string, string>>} [missingData] - * @prop {string} [partial] - * @prop {Record<string, Record<string, string>>} [partialData] - */ - -/** - * @typedef {Partial<Record<keyof FEATURES, MissingSupportResultStats>>} BrowserSupportStats - */ - -/** - * @typedef {Object} MissingSupportResult - * @prop {string[][]} browsers - * @prop {Partial<Record<keyof FEATURES, MissingSupportResultStats>>} features - */ - -export default class BrowserSelection { - #list; - - /** - * @param {string | string[]} [query] - * @param {string | false} [from] - */ - constructor(query, from) { - this.browsersRequest = query; - const options = from ? { path: from } : {}; - this.#list = browserslist(this.browsersRequest, options).map((browser) => browser.split(' ')); - } - - /** - * @param {string} browser - * @param {string} version - * @return {string[]|undefined} - */ - test(browser, version) { - const versions = version.split('-'); - - if (versions.length === 1) { - versions.push(versions[0]); - } - - return this.#list.find(([b, v]) => b === browser && v >= versions[0] && v <= versions[1]); - } - - list() { - return [...this.#list]; - } - - /** - * @param {Record<string, Record<string, string>>} browserStats - * @return {string} - */ - static lackingBrowsers(browserStats) { - const result = []; - for (const browser of Object.keys(browserStats)) { - result.push(formatBrowserName(browser, Object.keys(browserStats[browser]))); - } - return result.join(', '); - } - - /** - * @param {import('caniuse-lite').StatsByAgentID} stats - * @return {Filter<Record<string,Record<string, string>>>} - */ - filterStats(stats) { - /** @type {Filter<Record<string,Record<string, string>>>} */ - const result = { missing: {}, partial: {} }; - for (const [browser, versions] of Object.entries(stats)) { - /** @type {Filter<Record<string,string>>} */ - const feature = { missing: {}, partial: {} }; - for (const [version, support] of Object.entries(versions)) { - const selected = this.test(browser, version); - - // check if browser is NOT fully (i.e., don't have 'y' in their stats) supported - if (!selected) continue; - if ((/(^|\s)y($|\s)/.test(support))) continue; - // when it's not partially supported ('a'), it's missing - const type = (/(^|\s)a($|\s)/.test(support) ? 'partial' : 'missing'); - - if (!feature[type]) { - feature[type] = {}; - } - - feature[type][selected[1]] = support; - } - if (Object.keys(feature.missing).length > 0) { - result.missing[browser] = feature.missing; - } - - if (Object.keys(feature.partial).length > 0) { - result.partial[browser] = feature.partial; - } - } - return result; - } - - /** - * Get data on CSS features not supported by the given autoprefixer-like - * browser selection. - * @return {BrowserSupportStats} `features` is an array of: - * ``` - * { - * 'feature-name': { - * title: 'Title of feature' - * missing: "IE (8), Chrome (31)" - * missingData: { - * // map of browser -> version -> (lack of)support code - * ie: { '8': 'n' }, - * chrome: { '31': 'n' } - * } - * partialData: { - * // map of browser -> version -> (partial)support code - * ie: { '7': 'a' }, - * ff: { '29': 'a #1' } - * } - * caniuseData: { - * // caniuse-db json data for this feature - * } - * }, - * 'feature-name-2': {} // etc. - * } - * ``` - * - * `feature-name` is a caniuse-db slug. - */ - compileBrowserSupport() { - /** @type {Partial<Record<keyof FEATURES, MissingSupportResultStats>>} */ - const result = {}; - - for (const key of Object.keys(FEATURES)) { - const feature = /** @type {keyof FEATURES} */ (key); - const packedFeature = caniuse.features[feature]; - const featureData = caniuse.feature(packedFeature); - const lackData = this.filterStats(featureData.stats); - const missingData = lackData.missing; - const partialData = lackData.partial; - // browsers with missing or partial support for this feature - const missing = BrowserSelection.lackingBrowsers(missingData); - const partial = BrowserSelection.lackingBrowsers(partialData); - - if (missing.length > 0 || partial.length > 0) { - result[feature] = { - title: featureData.title, - caniuseData: featureData, - ...(missing.length > 0 ? { missingData, missing } : null), - ...(partial.length > 0 ? { partialData, partial } : null), - }; - } - } - - return result; - } - - /** - * @see BrowserSelection.compileBrowserSupport - * @param {ConstructorParameters<typeof this>} constructorParameters - * @return {MissingSupportResult} `{browsers, features}` - */ - static missingSupport(...constructorParameters) { - const selection = new BrowserSelection(...constructorParameters); - return { - browsers: selection.list(), - features: selection.compileBrowserSupport(), - }; - } -} diff --git a/node_modules/doiuse/lib/Detector.js b/node_modules/doiuse/lib/Detector.js deleted file mode 100644 index 2858bca9..00000000 --- a/node_modules/doiuse/lib/Detector.js +++ /dev/null @@ -1,157 +0,0 @@ -import FEATURES from '../data/features.js'; -import { performFeatureCheck } from '../utils/util.js'; - -/** - * @typedef DetectorCallbackArgument - * @prop {!import('postcss').ChildNode} usage - * @prop {keyof FEATURES} feature - * @prop {(keyof FEATURES & string)[]} ignore - */ - -/** - * @callback DetectorCallback - * @param {DetectorCallbackArgument} result - * @return {any} - */ - -const PLUGIN_OPTION_COMMENT = 'doiuse-'; -const DISABLE_FEATURE_COMMENT = `${PLUGIN_OPTION_COMMENT}disable`; -const ENABLE_FEATURE_COMMENT = `${PLUGIN_OPTION_COMMENT}enable`; - -/** - * Strip the contents of url literals so they aren't matched - * by our naive substring matching. - * @param {string} input - * @return {string} - */ -function stripUrls(input) { - return input.replaceAll(/url\([^)]*\)/g, 'url()'); -} - -/** - * Detect the use of any of a given list of CSS features. - * ``` - * var detector = new Detector(featureList) - * detector.process(css, cb) - * ``` - * - * `featureList`: an array of feature slugs (see caniuse-db) - * `cb`: a callback that gets called for each usage of one of the given features, - * called with an argument like: - * ``` - * { - * usage: {} // postcss node where usage was found - * feature: {} // caniuse-db feature slug - * ignore: {} // caniuse-db feature to ignore in current file - * } - * ``` - */ -export default class Detector { - /** - * @param {(keyof FEATURES & string)[]} featureList an array of feature slugs (see caniuse-db) - */ - constructor(featureList) { - /** @type {Partial<FEATURES>} */ - this.features = {}; - for (const feature of featureList) { - if (FEATURES[feature]) { - this.features[feature] = FEATURES[feature]; - } - } - /** @type {(keyof FEATURES & string)[]} */ - this.ignore = []; - } - - /** - * @param {import('postcss').Comment} comment - * @return {void} - */ - comment(comment) { - const text = comment.text.toLowerCase(); - - if (!text.startsWith(PLUGIN_OPTION_COMMENT)) return; - const option = text.split(' ', 1)[0]; - const value = text.replace(option, '').trim(); - - switch (option) { - case DISABLE_FEATURE_COMMENT: { - if (value === '') { - // @ts-expect-error Skip cast - this.ignore = Object.keys(this.features); - } else { - for (const feat of value.split(',')) { - /** @type {any} */ - const f = feat.trim(); - if (!this.ignore.includes(f)) { - this.ignore.push(f); - } - } - } - break; - } - case ENABLE_FEATURE_COMMENT: { - if (value === '') { - this.ignore = []; - } else { - const without = new Set(value.split(',').map((feat) => feat.trim())); - this.ignore = this.ignore.filter((index) => !without.has(index)); - } - break; - } - default: - } - } - - /** - * @param {import('postcss').Container} node - * @param {DetectorCallback} callback - * @return {void} - */ - node(node, callback) { - node.each((child) => { - if (child.type === 'comment') { - this.comment(child); - return; - } - - for (const [feat] of Object.entries(this.features).filter(([, featValue]) => { - if (!featValue) return false; - if (typeof featValue === 'function') { - return featValue(child); - } - if (Array.isArray(featValue)) { - return featValue.some((function_) => function_(child)); - } - if (child.type !== 'decl') { - return false; - } - - return Object.entries(featValue).some(([property, value]) => { - if (property !== '' && property !== child.prop) return false; - if (value === true) return true; - if (value === false) return false; - return performFeatureCheck(value, stripUrls(child.value)); - }); - })) { - const feature = /** @type {keyof FEATURES} */ (feat); - callback({ usage: child, feature, ignore: this.ignore }); - } - if (child.type !== 'decl') { - this.node(child, callback); - } - }); - } - - /** - * @param {import('postcss').Root} node - * @param {DetectorCallback} callback - * @return {void} - */ - process(node, callback) { - // Reset ignoring rules specified by inline comments per each file - this.ignore = []; - - // Recursively walk nodes in file - this.node(node, callback); - } -} diff --git a/node_modules/doiuse/lib/DoIUse.js b/node_modules/doiuse/lib/DoIUse.js deleted file mode 100644 index 78db53c8..00000000 --- a/node_modules/doiuse/lib/DoIUse.js +++ /dev/null @@ -1,130 +0,0 @@ -import multimatch from 'multimatch'; - -import BrowserSelection from './BrowserSelection.js'; -import Detector from './Detector.js'; - -/** @typedef {import('../data/features.js').FeatureKeys} FeatureKeys */ - -/** - * @typedef {Object} OnFeatureUsageArguments - * @prop {FeatureKeys} feature - * @prop {import('./BrowserSelection.js').MissingSupportResultStats|undefined} featureData - * @prop {import('postcss').Node} usage - * @prop {string} message - */ - -/** - * @callback OnFeatureUsage - * @param {OnFeatureUsageArguments} result - * @return {any} - */ - -/** - * @typedef {Object} DoIUseOptions - * @prop {ConstructorParameters<typeof BrowserSelection>[0]} [browsers] - * @prop {FeatureKeys[]} [ignore] - * @prop {OnFeatureUsage} [onFeatureUsage] - * @prop {string[]} [ignoreFiles] - */ - -export default class DoIUse { - static default = null; - - /** - * @param {DoIUseOptions} [optionsOrBrowserQuery] - */ - constructor(optionsOrBrowserQuery) { - const options = (typeof optionsOrBrowserQuery === 'string') - ? { browsers: optionsOrBrowserQuery } - : { ...optionsOrBrowserQuery }; - this.browserQuery = options.browsers; - this.onFeatureUsage = options.onFeatureUsage; - this.ignoreOptions = options.ignore; - this.ignoreFiles = options.ignoreFiles; - this.info = this.info.bind(this); - this.postcss = this.postcss.bind(this); - } - - /** - * @param {Object} [options] - * @param {ConstructorParameters<typeof BrowserSelection>[1]} [options.from] - */ - info(options = {}) { - const { browsers, features } = BrowserSelection.missingSupport(this.browserQuery, options.from); - - return { - browsers, - features, - }; - } - - /** @type {import('postcss').TransformCallback} */ - postcss(css, result) { - let from; - if (css.source && css.source.input) { - from = css.source.input.file; - } - const { features } = BrowserSelection.missingSupport(this.browserQuery, from); - // @ts-expect-error Needs cast - const detector = new Detector(Object.keys(features)); - - return detector.process(css, ({ feature, usage, ignore }) => { - if (ignore && ignore.includes(feature)) { - return; - } - - if (this.ignoreOptions && this.ignoreOptions.includes(feature)) { - return; - } - - if (!usage.source) { - throw new Error('No source?'); - } - if (this.ignoreFiles && multimatch(usage.source.input.from, this.ignoreFiles).length > 0) { - return; - } - - const data = features[feature]; - if (!data) { - throw new Error('No feature data?'); - } - const messages = []; - if (data.missing) { - messages.push(`not supported by: ${data.missing}`); - } - if (data.partial) { - messages.push(`only partially supported by: ${data.partial}`); - } - - let message = `${data.title} ${messages.join(' and ')} (${feature})`; - - result.warn(message, { node: usage, plugin: 'doiuse' }); - - if (this.onFeatureUsage) { - if (!usage.source) { - throw new Error('No usage source?'); - } - const { start, input } = usage.source; - if (!start) { - throw new Error('No usage source start?'); - } - - const map = css.source && css.source.input.map; - const mappedStart = map && map.consumer().originalPositionFor(start); - - const file = (mappedStart && mappedStart.source) || input.file || input.from; - const line = (mappedStart && mappedStart.line) || start.line; - const column = (mappedStart && mappedStart.column) || start.column; - - message = `${file}:${line}:${column}: ${message}`; - - this.onFeatureUsage({ - feature, - featureData: features[feature], - usage, - message, - }); - } - }); - } -} diff --git a/node_modules/doiuse/lib/stream/CssUsageDuplex.js b/node_modules/doiuse/lib/stream/CssUsageDuplex.js deleted file mode 100644 index 063ed1dc..00000000 --- a/node_modules/doiuse/lib/stream/CssUsageDuplex.js +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable no-underscore-dangle */ - -import { pipeline } from 'node:stream'; - -import Tokenize from 'css-tokenize'; -import Duplexify from 'duplexify'; - -import RuleUsageTransform from './RuleUsageTransform.js'; -import SourceMapTransform from './SourceMapTransform.js'; - -/** @typedef {import('../../data/features.js').FeatureKeys} FeatureKeys */ - -export default class CssUsageDuplex extends Duplexify { - /** - * @param {Object} options - * @param {string} options.browsers - * @param {FeatureKeys[]} [options.ignore] - * @param {boolean} [options.skipErrors] - * @param {string} [filename] Filename for outputting source code locations. - */ - constructor(options, filename) { - const streams = [ - new Tokenize(), - new SourceMapTransform(), - new RuleUsageTransform(options, filename), - ]; - pipeline( - // @ts-expect-error Bad typings - ...streams, - () => { - // noop (Error handled by Duplexify) - }, - ); - super(streams[0], streams.at(-1), { objectMode: true }); - } -} diff --git a/node_modules/doiuse/lib/stream/RuleUsageTransform.js b/node_modules/doiuse/lib/stream/RuleUsageTransform.js deleted file mode 100644 index a2a79d4d..00000000 --- a/node_modules/doiuse/lib/stream/RuleUsageTransform.js +++ /dev/null @@ -1,80 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { Transform } from 'node:stream'; - -import postcss from 'postcss'; -import sourcemap from 'source-map'; - -import DoIUse from '../DoIUse.js'; - -/** @typedef {import('../../data/features.js').FeatureKeys} FeatureKeys */ -/** @typedef {import('./SourceMapTransform.js').SourceMapTransformation} SourceMapTransformation */ - -export default class RuleUsageTransform extends Transform { - /** @type {import("postcss").Processor} */ - #processor; - - #skipErrors; - - #filename; - - /** - * @param {Object} options - * @param {string} options.browsers - * @param {FeatureKeys[]} [options.ignore] - * @param {boolean} [options.skipErrors] - * @param {string} [filename] Filename for outputting source code locations. - */ - constructor(options, filename = '<streaming css input>') { - super({ - objectMode: true, - }); - this.#processor = postcss([new DoIUse({ - browsers: options.browsers, - ignore: options.ignore, - onFeatureUsage: (usage) => { - this.push(usage); - }, - })]); - this.#filename = filename; - this.#skipErrors = options.skipErrors; - } - - /** - * @param {SourceMapTransformation} rule - * @param {any} encoding - * @param {Function} callback - */ - async _transform(rule, encoding, callback) { - let caughtError; - try { - const mapper = new sourcemap.SourceMapGenerator(); - const lines = rule.content.split('\n'); - let oline = rule.line; - let ocol = rule.column; - for (const [lineNumber, content] of lines.entries()) { - mapper.addMapping({ - generated: { line: lineNumber + 1, column: 1 }, - original: { line: oline, column: ocol }, - source: this.#filename, - }); - mapper.addMapping({ - generated: { line: lineNumber + 1, column: content.length }, - original: { line: oline, column: ocol + content.length }, - source: this.#filename, - }); - oline += 1; - ocol = 1; - } - await this.#processor.process(rule.content, { - from: this.#filename, map: { prev: mapper.toString() }, - }); - } catch (error) { - if (this.#skipErrors) { - this.emit('warning', error); - } else { - caughtError = error; - } - } - callback(caughtError); - } -} diff --git a/node_modules/doiuse/lib/stream/SourceMapTransform.js b/node_modules/doiuse/lib/stream/SourceMapTransform.js deleted file mode 100644 index 6295b1d3..00000000 --- a/node_modules/doiuse/lib/stream/SourceMapTransform.js +++ /dev/null @@ -1,74 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { Transform } from 'node:stream'; - -const NEW_LINE_CHAR = '\n'.codePointAt(0); - -/** - * @typedef {Object} SourceMapTransformation - * @prop {number} line - * @prop {number} column - * @prop {string} content - */ - -export default class SourceMapTransform extends Transform { - constructor() { - super({ objectMode: true }); - } - - /** - * @type {?{location:[number,number], buffers: Buffer[]}} - * buffer for the current incoming rule. - */ - #current = null; - - /** track depth to handle rules nested in at-rules. */ - #depth = 0; - - /** track this and pass it downstream for source mapping. */ - #line = 1; - - /** track this and pass it downstream for source mapping. */ - #column = 1; - - /** - * @param {[string,Buffer]} chunk - * @param {any} encoding - * @param {Function} callback - */ - _transform([type, buf], encoding, callback) { - if ((type === 'rule_start' || type === 'atrule_start')) { - this.#depth += 1; - } - if (this.#depth > 0 && !this.#current) { - this.#current = { location: [this.#line, this.#column], buffers: [] }; - } - if (type === 'rule_end' || type === 'atrule_end') { - this.#depth -= 1; - } - - if (this.#current) { - this.#current.buffers.push(buf); - if (this.#depth === 0) { - // Push rule - this.push({ - line: this.#current.location[0], - column: this.#current.location[1], - content: Buffer.concat(this.#current.buffers).toString(), - }); - this.#current = null; - } - } - - // Update position - for (const char of buf) { - if (char === NEW_LINE_CHAR) { - this.#line += 1; - this.#column = 1; - } else { - this.#column += 1; - } - } - - callback(); - } -} |
