Upstream postcss/autoprefixer state of the touched files before 42b5e28ba2

This commit is contained in:
upstream committed 2026-10-05 23:11:46 +02:00
1 parent bb453f7a17
commit e47232149a
6 files changed
+1083 -19

No files matched your search

+477
View File
@@ -0,0 +1,477 @@
let parser = require('postcss-value-parser')
let OldValue = require('../old-value')
let utils = require('../utils')
let Value = require('../value')
const IS_DIRECTION = /top|left|right|bottom/gi
class Gradient extends Value {
/**
* Do not add non-webkit prefixes for list-style and object
*/
add(decl, prefix, prefixes, result) {
let p = decl.prop
if (p.includes('mask')) {
if (prefix === '-webkit-' || prefix === '-webkit- old') {
return super.add(decl, prefix, prefixes, result)
}
} else if (
p === 'list-style' ||
p === 'list-style-image' ||
p === 'content'
) {
if (prefix === '-webkit-' || prefix === '-webkit- old') {
return super.add(decl, prefix, prefixes, result)
}
} else {
return super.add(decl, prefix, prefixes, result)
}
return undefined
}
/**
* Get div token from exists parameters
*/
cloneDiv(params) {
for (let i of params) {
if (i.type === 'div' && i.value === ',') {
return i
}
}
return { after: ' ', type: 'div', value: ',' }
}
/**
* Change colors syntax to old webkit
*/
colorStops(params) {
let result = []
for (let i = 0; i < params.length; i++) {
let pos
let param = params[i]
let item
if (i === 0) {
continue
}
if (param.length === 0) {
continue
}
let color = parser.stringify(param[0])
if (param[1] && param[1].type === 'word') {
pos = param[1].value
} else if (param[2] && param[2].type === 'word') {
pos = param[2].value
}
let stop
if (i === 1 && (!pos || pos === '0%')) {
stop = `from(${color})`
} else if (i === params.length - 1 && (!pos || pos === '100%')) {
stop = `to(${color})`
} else if (pos) {
stop = `color-stop(${pos}, ${color})`
} else {
stop = `color-stop(${color})`
}
let div = param[param.length - 1]
params[i] = [{ type: 'word', value: stop }]
if (div && div.type === 'div' && div.value === ',') {
item = params[i].push(div)
}
result.push(item)
}
return result
}
/**
* Change new direction to old
*/
convertDirection(params) {
if (params.length > 0) {
if (params[0].value === 'to') {
this.fixDirection(params)
} else if (params[0].value.includes('deg')) {
this.fixAngle(params)
} else if (this.isRadial(params)) {
this.fixRadial(params)
}
}
return params
}
/**
* Add 90 degrees
*/
fixAngle(params) {
let first = params[0].value
first = parseFloat(first)
first = Math.abs(450 - first) % 360
first = this.roundFloat(first, 3)
params[0].value = `${first}deg`
}
/**
* Replace `to top left` to `bottom right`
*/
fixDirection(params) {
params.splice(0, 2)
for (let param of params) {
if (param.type === 'div') {
break
}
if (param.type === 'word') {
param.value = this.revertDirection(param.value)
}
}
}
/**
* Fix radial direction syntax
*/
fixRadial(params) {
let first = []
let second = []
let a, b, c, i, next
for (i = 0; i < params.length - 2; i++) {
a = params[i]
b = params[i + 1]
c = params[i + 2]
if (a.type === 'space' && b.value === 'at' && c.type === 'space') {
next = i + 3
break
} else {
first.push(a)
}
}
let div
for (i = next; i < params.length; i++) {
if (params[i].type === 'div') {
div = params[i]
break
} else {
second.push(params[i])
}
}
params.splice(0, i, ...second, div, ...first)
}
/**
* Look for at word
*/
isRadial(params) {
let state = 'before'
for (let param of params) {
if (state === 'before' && param.type === 'space') {
state = 'at'
} else if (state === 'at' && param.value === 'at') {
state = 'after'
} else if (state === 'after' && param.type === 'space') {
return true
} else if (param.type === 'div') {
break
} else {
state = 'before'
}
}
return false
}
/**
* Replace old direction to new
*/
newDirection(params) {
if (!params[0]) {
return params
}
if (params[0].value === 'to') {
return params
}
IS_DIRECTION.lastIndex = 0 // reset search index of global regexp
if (!IS_DIRECTION.test(params[0].value)) {
return params
}
params.unshift(
{
type: 'word',
value: 'to'
},
{
type: 'space',
value: ' '
}
)
for (let i = 2; i < params.length; i++) {
if (params[i].type === 'div') {
break
}
if (params[i].type === 'word') {
params[i].value = this.revertDirection(params[i].value)
}
}
return params
}
/**
* Normalize angle
*/
normalize(nodes, gradientName) {
if (!nodes[0]) return nodes
if (/-?\d+(.\d+)?grad/.test(nodes[0].value)) {
nodes[0].value = this.normalizeUnit(nodes[0].value, 400)
} else if (/-?\d+(.\d+)?rad/.test(nodes[0].value)) {
nodes[0].value = this.normalizeUnit(nodes[0].value, 2 * Math.PI)
} else if (/-?\d+(.\d+)?turn/.test(nodes[0].value)) {
nodes[0].value = this.normalizeUnit(nodes[0].value, 1)
} else if (nodes[0].value.includes('deg')) {
let num = parseFloat(nodes[0].value)
num = ((num % 360) + 360) % 360
nodes[0].value = `${num}deg`
}
if (
gradientName === 'linear-gradient' ||
gradientName === 'repeating-linear-gradient'
) {
let direction = nodes[0].value
// Unitless zero for `<angle>` values are allowed in CSS gradients and transforms.
// Spec: https://github.com/w3c/csswg-drafts/commit/602789171429b2231223ab1e5acf8f7f11652eb3
if (direction === '0deg' || direction === '0') {
nodes = this.replaceFirst(nodes, 'to', ' ', 'top')
} else if (direction === '90deg') {
nodes = this.replaceFirst(nodes, 'to', ' ', 'right')
} else if (direction === '180deg') {
nodes = this.replaceFirst(nodes, 'to', ' ', 'bottom') // default value
} else if (direction === '270deg') {
nodes = this.replaceFirst(nodes, 'to', ' ', 'left')
}
}
return nodes
}
/**
* Convert angle unit to deg
*/
normalizeUnit(str, full) {
let num = parseFloat(str)
let deg = (num / full) * 360
return `${deg}deg`
}
/**
* Remove old WebKit gradient too
*/
old(prefix) {
if (prefix === '-webkit-') {
let type
if (this.name === 'linear-gradient') {
type = 'linear'
} else if (this.name === 'repeating-linear-gradient') {
type = 'repeating-linear'
} else if (this.name === 'repeating-radial-gradient') {
type = 'repeating-radial'
} else {
type = 'radial'
}
let string = '-gradient'
let regexp = utils.regexp(
`-webkit-(${type}-gradient|gradient\\(\\s*${type})`,
false
)
return new OldValue(this.name, prefix + this.name, string, regexp)
} else {
return super.old(prefix)
}
}
/**
* Change direction syntax to old webkit
*/
oldDirection(params) {
let div = this.cloneDiv(params[0])
if (!params[0][0] || params[0][0].value !== 'to') {
return params.unshift([
{ type: 'word', value: Gradient.oldDirections.bottom },
div
])
} else {
let words = []
for (let node of params[0].slice(2)) {
if (node.type === 'word') {
words.push(node.value.toLowerCase())
}
}
words = words.join(' ')
let old = Gradient.oldDirections[words] || words
params[0] = [{ type: 'word', value: old }, div]
return params[0]
}
}
/**
* Convert to old webkit syntax
*/
oldWebkit(node, decl, result) {
let { nodes } = node
let string = parser.stringify(node.nodes)
if (this.name !== 'linear-gradient') {
return false
}
if (nodes.length === 0) {
return false
}
if (nodes[0] && nodes[0].value.includes('deg')) {
return false
}
if (
string.includes('px') ||
string.includes('-corner') ||
string.includes('-side')
) {
return false
}
if (string.includes('var(')) {
return false
}
let params = [[]]
for (let i of nodes) {
params[params.length - 1].push(i)
if (i.type === 'div' && i.value === ',') {
params.push([])
}
}
this.oldDirection(params)
for (let param of params.slice(1)) {
let tokens = param.filter(i => i.type === 'word' || i.type === 'function')
if (
tokens.length === 1 &&
((tokens[0].type === 'word' && parser.unit(tokens[0].value)) ||
(tokens[0].type === 'function' &&
/^(calc|min|max|clamp)$/i.test(tokens[0].value)))
) {
if (decl && result) {
decl.warn(
result,
'Gradient color hints are not supported by old WebKit'
)
}
return false
}
}
this.colorStops(params)
node.nodes = []
for (let param of params) {
node.nodes.push(...param)
}
node.nodes.unshift(
{ type: 'word', value: 'linear' },
this.cloneDiv(node.nodes)
)
node.value = '-webkit-gradient'
return true
}
/**
* Change degrees for webkit prefix
*/
replace(string, prefix, decl, result) {
let ast = parser(string)
for (let node of ast.nodes) {
let gradientName = this.name // gradient name
if (node.type === 'function' && node.value === gradientName) {
node.nodes = this.newDirection(node.nodes)
node.nodes = this.normalize(node.nodes, gradientName)
if (prefix === '-webkit- old') {
let changes = this.oldWebkit(node, decl, result)
if (!changes) {
return false
}
} else {
node.nodes = this.convertDirection(node.nodes)
node.value = prefix + node.value
}
}
}
return ast.toString()
}
/**
* Replace first token
*/
replaceFirst(params, ...words) {
let prefix = words.map(i => {
if (i === ' ') {
return { type: 'space', value: i }
}
return { type: 'word', value: i }
})
return prefix.concat(params.slice(1))
}
revertDirection(word) {
return Gradient.directions[word.toLowerCase()] || word
}
/**
* Round float and save digits under dot
*/
roundFloat(float, digits) {
return parseFloat(float.toFixed(digits))
}
}
Gradient.names = [
'linear-gradient',
'repeating-linear-gradient',
'radial-gradient',
'repeating-radial-gradient'
]
Gradient.directions = {
bottom: 'top',
left: 'right',
right: 'left',
top: 'bottom' // default value
}
// Direction to replace
Gradient.oldDirections = {
'bottom': 'left top, left bottom',
'bottom left': 'right top, left bottom',
'bottom right': 'left top, right bottom',
'left': 'right top, left top',
'left bottom': 'right top, left bottom',
'left top': 'right bottom, left top',
'right': 'left top, right top',
'right bottom': 'left top, right bottom',
'right top': 'left bottom, right top',
'top': 'left bottom, left top',
'top left': 'right bottom, left top',
'top right': 'left bottom, right top'
}
module.exports = Gradient
+52
View File
@@ -0,0 +1,52 @@
let Declaration = require('../declaration')
let { isPureNumber } = require('../utils')
class GridEnd extends Declaration {
/**
* Change repeating syntax for IE
*/
insert(decl, prefix, prefixes, result) {
if (prefix !== '-ms-') return super.insert(decl, prefix, prefixes)
let clonedDecl = this.clone(decl)
let startProp = decl.prop.replace(/end$/, 'start')
let spanProp = prefix + decl.prop.replace(/end$/, 'span')
if (decl.parent.some(i => i.prop === spanProp)) {
return undefined
}
clonedDecl.prop = spanProp
if (decl.value.includes('span')) {
clonedDecl.value = decl.value.replace(/span\s/i, '')
} else {
let startDecl
decl.parent.walkDecls(startProp, d => {
startDecl = d
})
if (startDecl) {
if (isPureNumber(startDecl.value)) {
let value = Number(decl.value) - Number(startDecl.value) + ''
clonedDecl.value = value
} else {
return undefined
}
} else {
decl.warn(
result,
`Can not prefix ${decl.prop} (${startProp} is not found)`
)
}
}
decl.cloneBefore(clonedDecl)
return undefined
}
}
GridEnd.names = ['grid-row-end', 'grid-column-end']
module.exports = GridEnd
+125
View File
@@ -0,0 +1,125 @@
let Declaration = require('../declaration')
let Processor = require('../processor')
let {
autoplaceGridItems,
getGridGap,
inheritGridGap,
prefixTrackProp,
prefixTrackValue
} = require('./grid-utils')
class GridRowsColumns extends Declaration {
insert(decl, prefix, prefixes, result) {
if (prefix !== '-ms-') return super.insert(decl, prefix, prefixes)
let { parent, prop, value } = decl
let isRowProp = prop.includes('rows')
let isColumnProp = prop.includes('columns')
let hasGridTemplate = parent.some(
i => i.prop === 'grid-template' || i.prop === 'grid-template-areas'
)
/**
* Not to prefix rows declaration if grid-template(-areas) is present
*/
if (hasGridTemplate && isRowProp) {
return false
}
let processor = new Processor({ options: {} })
let status = processor.gridStatus(parent, result)
let gap = getGridGap(decl)
gap = inheritGridGap(decl, gap) || gap
let gapValue = isRowProp ? gap.row : gap.column
if ((status === 'no-autoplace' || status === true) && !hasGridTemplate) {
gapValue = null
}
let prefixValue = prefixTrackValue({
gap: gapValue,
value
})
/**
* Insert prefixes
*/
decl.cloneBefore({
prop: prefixTrackProp({ prefix, prop }),
value: prefixValue
})
let autoflow = parent.nodes.find(i => i.prop === 'grid-auto-flow')
let autoflowValue = 'row'
if (autoflow && !processor.disabled(autoflow, result)) {
autoflowValue = autoflow.value.trim()
}
if (status === 'autoplace') {
/**
* Show warning if grid-template-rows decl is not found
*/
let rowDecl = parent.nodes.find(i => i.prop === 'grid-template-rows')
if (!rowDecl && hasGridTemplate) {
return undefined
} else if (!rowDecl && !hasGridTemplate) {
decl.warn(
result,
'Autoplacement does not work without grid-template-rows property'
)
return undefined
}
/**
* Show warning if grid-template-columns decl is not found
*/
let columnDecl = parent.nodes.find(i => {
return i.prop === 'grid-template-columns'
})
if (!columnDecl && !hasGridTemplate) {
decl.warn(
result,
'Autoplacement does not work without grid-template-columns property'
)
}
/**
* Autoplace grid items
*/
if (isColumnProp && !hasGridTemplate) {
autoplaceGridItems(decl, result, gap, autoflowValue)
}
}
return undefined
}
/**
* Change IE property back
*/
normalize(prop) {
return prop.replace(/^grid-(rows|columns)/, 'grid-template-$1')
}
/**
* Change property name for IE
*/
prefixed(prop, prefix) {
if (prefix === '-ms-') {
return prefixTrackProp({ prefix, prop })
}
return super.prefixed(prop, prefix)
}
}
GridRowsColumns.names = [
'grid-template-rows',
'grid-template-columns',
'grid-rows',
'grid-columns'
]
module.exports = GridRowsColumns
+7 -19
View File
@@ -604,8 +604,7 @@ function insertAreas(css, isDisabled) {
Object.assign(i, {
raws: {
between: gridArea.raws.between
},
source: gridArea.source
}
})
)
)
@@ -627,8 +626,7 @@ function insertAreas(css, isDisabled) {
Object.assign(i, {
raws: {
between: gridArea.raws.between
},
source: gridArea.source
}
})
)
)
@@ -663,8 +661,7 @@ function insertAreas(css, isDisabled) {
Object.assign(i, {
raws: {
between: gridArea.raws.between
},
source: gridArea.source
}
})
)
)
@@ -686,8 +683,7 @@ function insertAreas(css, isDisabled) {
Object.assign(i, {
raws: {
between: gridArea.raws.between
},
source: gridArea.source
}
})
)
)
@@ -736,11 +732,7 @@ function insertAreas(css, isDisabled) {
.filter(p => p !== 'lastRule')
.forEach(params => {
if (data[params].length > 0 && lastRule) {
lastRule.after({
name: 'media',
params,
source: data[params][0].source
})
lastRule.after({ name: 'media', params })
lastRule.next().append(data[params])
}
})
@@ -1172,12 +1164,8 @@ function autoplaceGridItems(decl, result, gap, autoflowValue = 'row') {
node.selector = nodeSelector
// insert prefixed row/column values
node.append({ prop: '-ms-grid-row', source: decl.source, value: row.start })
node.append({
prop: '-ms-grid-column',
source: decl.source,
value: column.start
})
node.append({ prop: '-ms-grid-row', value: row.start })
node.append({ prop: '-ms-grid-column', value: column.start })
// insert rule
parent.after(node)
+329
View File
@@ -0,0 +1,329 @@
let { list } = require('postcss')
let parser = require('postcss-value-parser')
let Browsers = require('./browsers')
let vendor = require('./vendor')
class Transition {
constructor(prefixes) {
this.props = ['transition', 'transition-property']
this.prefixes = prefixes
}
/**
* Process transition and add prefixes for all necessary properties
*/
add(decl, result) {
let prefix, prop
let add = this.prefixes.add[decl.prop]
let vendorPrefixes = this.ruleVendorPrefixes(decl)
let declPrefixes = vendorPrefixes || (add && add.prefixes) || []
let params = this.parse(decl.value)
let names = params.map(i => this.findProp(i))
let added = []
if (names.some(i => i[0] === '-')) {
return
}
for (let param of params) {
prop = this.findProp(param)
if (prop[0] === '-') continue
let prefixer = this.prefixes.add[prop]
if (!prefixer || !prefixer.prefixes) continue
for (prefix of prefixer.prefixes) {
if (vendorPrefixes && !vendorPrefixes.some(p => prefix.includes(p))) {
continue
}
let prefixed = this.prefixes.prefixed(prop, prefix)
if (prefixed !== '-ms-transform' && !names.includes(prefixed)) {
if (!this.disabled(prop, prefix)) {
added.push(this.clone(prop, prefixed, param))
}
}
}
}
params = params.concat(added)
let value = this.stringify(params)
let webkitClean = this.stringify(
this.cleanFromUnprefixed(params, '-webkit-')
)
if (declPrefixes.includes('-webkit-')) {
this.cloneBefore(decl, `-webkit-${decl.prop}`, webkitClean)
}
this.cloneBefore(decl, decl.prop, webkitClean)
if (declPrefixes.includes('-o-')) {
let operaClean = this.stringify(this.cleanFromUnprefixed(params, '-o-'))
this.cloneBefore(decl, `-o-${decl.prop}`, operaClean)
}
for (prefix of declPrefixes) {
if (prefix !== '-webkit-' && prefix !== '-o-') {
let prefixValue = this.stringify(
this.cleanOtherPrefixes(params, prefix)
)
this.cloneBefore(decl, prefix + decl.prop, prefixValue)
}
}
if (value !== decl.value && !this.already(decl, decl.prop, value)) {
this.checkForWarning(result, decl)
decl.cloneBefore()
decl.value = value
}
}
/**
* Does we already have this declaration
*/
already(decl, prop, value) {
return decl.parent.some(i => i.prop === prop && i.value === value)
}
/**
* Show transition-property warning
*/
checkForWarning(result, decl) {
if (decl.prop !== 'transition-property') {
return
}
let isPrefixed = false
let hasAssociatedProp = false
decl.parent.each(i => {
if (i.type !== 'decl') {
return undefined
}
if (i.prop.indexOf('transition-') !== 0) {
return undefined
}
let values = list.comma(i.value)
// check if current Rule's transition-property comma separated value list needs prefixes
if (i.prop === 'transition-property') {
values.forEach(value => {
let lookup = this.prefixes.add[value]
if (lookup && lookup.prefixes && lookup.prefixes.length > 0) {
isPrefixed = true
}
})
return undefined
}
// check if another transition-* prop in current Rule has comma separated value list
hasAssociatedProp = hasAssociatedProp || values.length > 1
return false
})
if (isPrefixed && hasAssociatedProp) {
decl.warn(
result,
'Replace transition-property to transition, ' +
'because Autoprefixer could not support ' +
'any cases of transition-property ' +
'and other transition-*'
)
}
}
/**
* Remove all non-webkit prefixes and unprefixed params if we have prefixed
*/
cleanFromUnprefixed(params, prefix) {
let remove = params
.map(i => this.findProp(i))
.filter(i => i.slice(0, prefix.length) === prefix)
.map(i => this.prefixes.unprefixed(i))
let result = []
for (let param of params) {
let prop = this.findProp(param)
let p = vendor.prefix(prop)
if (!remove.includes(prop) && (p === prefix || p === '')) {
result.push(param)
}
}
return result
}
cleanOtherPrefixes(params, prefix) {
return params.filter(param => {
let current = vendor.prefix(this.findProp(param))
return current === '' || current === prefix
})
}
/**
* Return new param array with different name
*/
clone(origin, name, param) {
let result = []
let changed = false
for (let i of param) {
if (!changed && i.type === 'word' && i.value === origin) {
result.push({ type: 'word', value: name })
changed = true
} else {
result.push(i)
}
}
return result
}
/**
* Add declaration if it is not exist
*/
cloneBefore(decl, prop, value) {
if (!this.already(decl, prop, value)) {
decl.cloneBefore({ prop, value })
}
}
/**
* Check property for disabled by option
*/
disabled(prop, prefix) {
let other = ['order', 'justify-content', 'align-self', 'align-content']
if (prop.includes('flex') || other.includes(prop)) {
if (this.prefixes.options.flexbox === false) {
return true
}
if (this.prefixes.options.flexbox === 'no-2009') {
return prefix.includes('2009')
}
}
return undefined
}
/**
* Find or create separator
*/
div(params) {
for (let param of params) {
for (let node of param) {
if (node.type === 'div' && node.value === ',') {
return node
}
}
}
return { after: ' ', type: 'div', value: ',' }
}
/**
* Find property name
*/
findProp(param) {
let prop = param[0].value
if (/^\d/.test(prop)) {
for (let [i, token] of param.entries()) {
if (i !== 0 && token.type === 'word') {
return token.value
}
}
}
return prop
}
/**
* Parse properties list to array
*/
parse(value) {
let ast = parser(value)
let result = []
let param = []
for (let node of ast.nodes) {
param.push(node)
if (node.type === 'div' && node.value === ',') {
result.push(param)
param = []
}
}
result.push(param)
return result.filter(i => i.length > 0)
}
/**
* Process transition and remove all unnecessary properties
*/
remove(decl) {
let params = this.parse(decl.value)
params = params.filter(i => {
let prop = this.prefixes.remove[this.findProp(i)]
return !prop || !prop.remove
})
let value = this.stringify(params)
if (decl.value === value) {
return
}
if (params.length === 0) {
decl.remove()
return
}
let double = decl.parent.some(i => {
return i.prop === decl.prop && i.value === value
})
let smaller = decl.parent.some(i => {
return i !== decl && i.prop === decl.prop && i.value.length > value.length
})
if (double || smaller) {
decl.remove()
return
}
decl.value = value
}
/**
* Check if transition prop is inside vendor specific rule
*/
ruleVendorPrefixes(decl) {
let { parent } = decl
if (parent.type !== 'rule') {
return false
} else if (!parent.selector.includes(':-')) {
return false
}
let selectors = Browsers.prefixes().filter(s =>
parent.selector.includes(':' + s)
)
return selectors.length > 0 ? selectors : false
}
/**
* Return properties string from array
*/
stringify(params) {
if (params.length === 0) {
return ''
}
let nodes = []
for (let param of params) {
if (param[param.length - 1].type !== 'div') {
param.push(this.div(params))
}
nodes.push(...param)
}
if (nodes[0].type === 'div') {
nodes = nodes.slice(1)
}
if (nodes[nodes.length - 1].type === 'div') {
nodes = nodes.slice(0, -1)
}
return parser.stringify({ nodes })
}
}
module.exports = Transition
+93
View File
@@ -0,0 +1,93 @@
let { list } = require('postcss')
/**
* Throw special error, to tell beniary,
* that this error is from Autoprefixer.
*/
module.exports.error = function (text) {
let err = new Error(text)
err.autoprefixer = true
throw err
}
/**
* Return array, that doesn’t contain duplicates.
*/
module.exports.uniq = function (array) {
return [...new Set(array)]
}
/**
* Return "-webkit-" on "-webkit- old"
*/
module.exports.removeNote = function (string) {
if (!string.includes(' ')) {
return string
}
return string.split(' ')[0]
}
/**
* Escape RegExp symbols
*/
module.exports.escapeRegexp = function (string) {
return string.replace(/[$()*+-.?[\\\]^{|}]/g, '\\$&')
}
/**
* Return regexp to check, that CSS string contain word
*/
module.exports.regexp = function (word, escape = true) {
if (escape) {
word = this.escapeRegexp(word)
}
return new RegExp(`(^|[\\s,(])(${word}($|[\\s(,]))`, 'gi')
}
/**
* Change comma list
*/
module.exports.editList = function (value, callback) {
let origin = list.comma(value)
let changed = callback(origin, [])
if (origin === changed) {
return value
}
let join = value.match(/,\s*/)
join = join ? join[0] : ', '
return changed.join(join)
}
/**
* Split the selector into parts.
* It returns 3 level deep array because selectors can be comma
* separated (1), space separated (2), and combined (3)
* @param {String} selector selector string
* @return {Array<Array<Array>>} 3 level deep array of split selector
* @see utils.test.js for examples
*/
module.exports.splitSelector = function (selector) {
return list.comma(selector).map(i => {
return list.space(i).map(k => {
return k.split(/(?=\.|#)/g)
})
})
}
/**
* Return true if a given value only contains numbers.
* @param {*} value
* @returns {boolean}
*/
module.exports.isPureNumber = function (value) {
if (typeof value === 'number') {
return true
}
if (typeof value === 'string') {
return /^[0-9]+$/.test(value)
}
return false
}