Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy over utils missing in stylelint 15.10.0 #13

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/rules/media-feature-name-case/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
const { createPlugin } = require('stylelint')
const atRuleParamIndex = require('stylelint/lib/utils/atRuleParamIndex')
const isCustomMediaQuery = require('stylelint/lib/utils/isCustomMediaQuery')
const isRangeContextMediaFeature = require('stylelint/lib/utils/isRangeContextMediaFeature')
const isStandardSyntaxMediaFeatureName = require('stylelint/lib/utils/isStandardSyntaxMediaFeatureName')
const mediaParser = require('postcss-media-query-parser').default
const rangeContextNodeParser = require('stylelint/lib/rules/rangeContextNodeParser')
const report = require('stylelint/lib/utils/report')
const ruleMessages = require('stylelint/lib/utils/ruleMessages')
const validateOptions = require('stylelint/lib/utils/validateOptions')
const rangeContextNodeParser = require('../../utils/rangeContextNodeParser')
const isRangeContextMediaFeature = require('../../utils/isRangeContextMediaFeature')

const ruleName = 'stylistic/media-feature-name-case'

Expand Down
9 changes: 9 additions & 0 deletions lib/utils/isRangeContextMediaFeature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Check whether a media feature is a range context one
*
* @param {string} mediaFeature feature
* @return {boolean} If `true`, media feature is a range context one
*/
module.exports = function isRangeContextMediaFeature(mediaFeature) {
return mediaFeature.includes('=') || mediaFeature.includes('<') || mediaFeature.includes('>')
}
57 changes: 57 additions & 0 deletions lib/utils/rangeContextNodeParser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const valueParser = require('postcss-value-parser')

const { assert } = require('./validateTypes')

const rangeOperators = new Set(['>=', '<=', '>', '<', '='])

/**
* @param {string} name
* @returns {boolean}
*/
function isRangeContextName(name) {
// When the node is like "(width > 10em)" or "(10em < width)"
// Regex is needed because the name can either be in the first or second position
return /^(?!--)\D/.test(name) || /^--./.test(name)
}

/**
* @typedef {{ value: string, sourceIndex: number }} RangeContextNode
*
* @param {import('postcss-media-query-parser').Node} node
* @returns {{ name: RangeContextNode, values: RangeContextNode[] }}
*/
module.exports = function rangeContextNodeParser(node) {
/** @type {import('postcss-value-parser').WordNode | undefined} */
let nameNode

/** @type {import('postcss-value-parser').WordNode[]} */
const valueNodes = []

valueParser(node.value).walk((valueNode) => {
if (valueNode.type !== 'word') return

if (rangeOperators.has(valueNode.value)) return

if (nameNode == null && isRangeContextName(valueNode.value)) {
nameNode = valueNode

return
}

valueNodes.push(valueNode)
})

assert(nameNode)

return {
name: {
value: nameNode.value,
sourceIndex: node.sourceIndex + nameNode.sourceIndex,
},

values: valueNodes.map((valueNode) => ({
value: valueNode.value,
sourceIndex: node.sourceIndex + valueNode.sourceIndex,
})),
}
}
15 changes: 15 additions & 0 deletions lib/utils/tests/isRangeContextMediaFeature.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const isRangeContextMediaFeature = require('../isRangeContextMediaFeature')

it('isRangeContextMediaFeature', () => {
expect(isRangeContextMediaFeature('(width = 10px)')).toBeTruthy()
expect(isRangeContextMediaFeature('(width > 10px)')).toBeTruthy()
expect(isRangeContextMediaFeature('(width < 10px)')).toBeTruthy()
expect(isRangeContextMediaFeature('(HEIGHT >= 10px)')).toBeTruthy()
expect(isRangeContextMediaFeature('(HEIGHT <= 10px)')).toBeTruthy()
expect(isRangeContextMediaFeature('(5px > width < 10px)')).toBeTruthy()
expect(isRangeContextMediaFeature('(5px => HEIGHT <= 10px)')).toBeTruthy()
expect(isRangeContextMediaFeature('(5px > HEIGHT <= 10px)')).toBeTruthy()
expect(isRangeContextMediaFeature('(color)')).toBeFalsy()
expect(isRangeContextMediaFeature('(MONOCHROME)')).toBeFalsy()
expect(isRangeContextMediaFeature('(min-width: 10px)')).toBeFalsy()
})
110 changes: 110 additions & 0 deletions lib/utils/tests/validateTypes.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
const {
isBoolean,
isFunction,
isNullish,
isNumber,
isObject,
isRegExp,
isString,
isPlainObject,
} = require('../validateTypes')

describe('isBoolean()', () => {
it('returns true when a boolean value is specified', () => {
expect(isBoolean(true)).toBe(true)
})

it('returns true when a Boolean object is specified', () => {
expect(isBoolean(new Boolean(true))).toBe(true) // eslint-disable-line no-new-wrappers
})

it('returns false when a boolean value is not specified', () => {
expect(isBoolean(null)).toBe(false)
})
})

describe('isFunction()', () => {
it('returns true when a function value is specified', () => {
expect(isFunction(() => 1)).toBe(true)
})

it('returns true when a Function object is specified', () => {
expect(isFunction(new Function())).toBe(true) // eslint-disable-line no-new-func
})

it('returns false when a function value is specified', () => {
expect(isFunction(null)).toBe(false)
})
})

describe('isNullish()', () => {
it('returns true when null is specified', () => {
expect(isNullish(null)).toBe(true)
})

it('returns true when undefined is specified', () => {
expect(isNullish(undefined)).toBe(true)
})

it('returns false when neither null nor undefined is specified', () => {
expect(isNullish('')).toBe(false)
})
})

describe('isNumber()', () => {
it('returns true when a number value is specified', () => {
expect(isNumber(1)).toBe(true)
})

it('returns true when a Number object is specified', () => {
expect(isNumber(new Number(1))).toBe(true) // eslint-disable-line no-new-wrappers
})

it('returns false when a number value is not specified', () => {
expect(isNumber(null)).toBe(false)
})
})

describe('isObject()', () => {
it('returns true when an object is specified', () => {
expect(isObject({})).toBe(true)
})

it('returns false when an object is not specified', () => {
expect(isObject(null)).toBe(false)
})
})

describe('isRegExp()', () => {
it('returns true when a regexp value is specified', () => {
expect(isRegExp(/a/)).toBe(true)
})

it('returns false when a regexp value is not specified', () => {
expect(isRegExp(null)).toBe(false)
})
})

describe('isString()', () => {
it('returns true when a string value is specified', () => {
expect(isString('')).toBe(true)
})

it('returns true when a String object is specified', () => {
expect(isString(new String(''))).toBe(true) // eslint-disable-line no-new-wrappers
})

it('returns false when a string value is not specified', () => {
expect(isString(null)).toBe(false)
})
})

describe('isPlainObject()', () => {
it('returns true when a plain object is specified', () => {
expect(isPlainObject({})).toBe(true)
})

it('returns false when a plain object is not specified', () => {
expect(isPlainObject(null)).toBe(false)
})
})
136 changes: 136 additions & 0 deletions lib/utils/validateTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
const { isPlainObject: _isPlainObject } = require('is-plain-object')

/**
* Checks if the value is a boolean or a Boolean object.
* @param {unknown} value
* @returns {value is boolean}
*/
function isBoolean(value) {
return typeof value === 'boolean' || value instanceof Boolean
}

/**
* Checks if the value is a function or a Function object.
* @param {unknown} value
* @returns {value is Function}
*/
function isFunction(value) {
return typeof value === 'function' || value instanceof Function
}

/**
* Checks if the value is *nullish*.
* @see https://developer.mozilla.org/en-US/docs/Glossary/Nullish
* @param {unknown} value
* @returns {value is null | undefined}
*/
function isNullish(value) {
return value == null
}

/**
* Checks if the value is a number or a Number object.
* @param {unknown} value
* @returns {value is number}
*/
function isNumber(value) {
return typeof value === 'number' || value instanceof Number
}

/**
* Checks if the value is an object.
* @param {unknown} value
* @returns {value is object}
*/
function isObject(value) {
return value !== null && typeof value === 'object'
}

/**
* Checks if the value is a regular expression.
* @param {unknown} value
* @returns {value is RegExp}
*/
function isRegExp(value) {
return value instanceof RegExp
}

/**
* Checks if the value is a string or a String object.
* @param {unknown} value
* @returns {value is string}
*/
function isString(value) {
return typeof value === 'string' || value instanceof String
}

/**
* Checks if the value is a plain object.
* @param {unknown} value
* @returns {value is Record<string, unknown>}
*/
function isPlainObject(value) {
return _isPlainObject(value)
}

/**
* Assert that the value is truthy.
* @param {unknown} value
* @param {string} [message]
* @returns {asserts value}
*/
function assert(value, message = undefined) {
if (message) {
// eslint-disable-next-line no-console
console.assert(value, message)
} else {
// eslint-disable-next-line no-console
console.assert(value)
}
}

/**
* Assert that the value is a function or a Function object.
* @param {unknown} value
* @returns {asserts value is Function}
*/
function assertFunction(value) {
// eslint-disable-next-line no-console
console.assert(isFunction(value), `"${value}" must be a function`)
}

/**
* Assert that the value is a number or a Number object.
* @param {unknown} value
* @returns {asserts value is number}
*/
function assertNumber(value) {
// eslint-disable-next-line no-console
console.assert(isNumber(value), `"${value}" must be a number`)
}

/**
* Assert that the value is a string or a String object.
* @param {unknown} value
* @returns {asserts value is string}
*/
function assertString(value) {
// eslint-disable-next-line no-console
console.assert(isString(value), `"${value}" must be a string`)
}

module.exports = {
isBoolean,
isFunction,
isNullish,
isNumber,
isObject,
isRegExp,
isString,
isPlainObject,

assert,
assertFunction,
assertNumber,
assertString,
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"!dist/**/tests/*"
],
"dependencies": {
"is-plain-object": "^5.0.0",
"postcss": "^8.4.21",
"postcss-media-query-parser": "^0.2.3",
"postcss-value-parser": "^4.2.0",
Expand Down