diff --git a/edit/autocomplete.js b/edit/autocomplete.js index 14da5289..16b196d4 100644 --- a/edit/autocomplete.js +++ b/edit/autocomplete.js @@ -16,17 +16,11 @@ const rxVAR = /(^|[^-.\w\u0080-\uFFFF])var\(/iyu; const rxCONSUME = /([-\w]*\s*:\s?)?/yu; const cssMime = CodeMirror.mimeModes['text/css']; - const cssGlobalValues = [ - 'inherit', - 'initial', - 'revert', - 'unset', - ]; const docFuncs = addSuffix(cssMime.documentTypes, '('); const {tokenHooks} = cssMime; const originalCommentHook = tokenHooks['/']; const originalHelper = CodeMirror.hint.css || (() => {}); - let cssMedia, cssProps, cssPropsValues; + let cssMedia, cssProps, cssValues; const AOT_ID = 'autocompleteOnTyping'; const AOT_PREF_ID = 'editor.' + AOT_ID; @@ -148,8 +142,8 @@ leftLC = leftLC.replace(/^[^\w\s]\s*/, ''); } if (prop.startsWith('--')) prop = 'color'; // assuming 90% of variables are colors - if (!cssPropsValues) cssPropsValues = await linterMan.worker.getCssPropsValues(); - list = [...new Set([...cssPropsValues[prop] || [], ...cssGlobalValues])]; + if (!cssValues) cssValues = await linterMan.worker.getCssPropsValues(); + list = [...new Set([...cssValues.own[prop] || [], ...cssValues.global])]; end = prev + execAt(/(\s*[-a-z(]+)?/y, prev, text)[0].length; } } diff --git a/edit/editor-worker.js b/edit/editor-worker.js index ad150da1..54f81d49 100644 --- a/edit/editor-worker.js +++ b/edit/editor-worker.js @@ -18,7 +18,10 @@ getCssPropsValues() { require(['/js/csslint/parserlib']); /* global parserlib */ - const {css: {Colors, Properties}, util: {describeProp}} = parserlib; + const { + css: {Colors, GlobalKeywords, Properties}, + util: {describeProp}, + } = parserlib; const namedColors = Object.keys(Colors); const rxNonWord = /(?:<.+?>|[^-\w<(]+\d*)+/g; const res = {}; @@ -41,7 +44,7 @@ if (uniq.length) res[k] = uniq; } } - return res; + return {own: res, global: GlobalKeywords}; }, getRules(linter) { diff --git a/js/csslint/parserlib.js b/js/csslint/parserlib.js index 7babcaf7..ec17b3d1 100644 --- a/js/csslint/parserlib.js +++ b/js/csslint/parserlib.js @@ -30,12 +30,18 @@ self.parserlib = (() => { //#region Properties + // Global keywords that can be set for any property are conveniently listed in `all` prop: + // https://drafts.csswg.org/css-cascade/#all-shorthand + const GlobalKeywords = ['initial', 'inherit', 'revert', 'unset']; + const isGlobalKeyword = RegExp.prototype.test.bind( + new RegExp(`^(${GlobalKeywords.join('|')})$`, 'i')); + const Properties = { 'accent-color': 'auto | ', 'align-items': 'normal | stretch | | [ ? ]', 'align-content': '', 'align-self': '', - 'all': 'initial | inherit | revert | unset', + 'all': GlobalKeywords.join(' | '), 'alignment-adjust': 'auto | baseline | before-edge | text-before-edge | middle | central | ' + 'after-edge | text-after-edge | ideographic | alphabetic | hanging | ' + 'mathematical | ', @@ -702,8 +708,8 @@ self.parserlib = (() => { const VTSimple = { '': 'xx-small | x-small | small | medium | large | x-large | xx-large', '': 'scroll-position | contents | ', - '': p => vtIsIdent(p) && - !/^(unset|initial|inherit|will-change|auto|scroll-position|contents)$/i.test(p), + '': p => vtIsIdent(p) && !isGlobalKeyword(p) && + !/^(will-change|auto|scroll-position|contents)$/i.test(p), '': p => p.type === 'angle' || p.isCalc, '': p => p.text === '0' || p.type === 'angle' || p.isCalc, '': vtIsAttr, @@ -752,8 +758,8 @@ self.parserlib = (() => { '': p => p.tokenType === Tokens.HASH, //eslint-disable-line no-use-before-define '': 'cielab() | cielch() | cielchab() | icc-color() | icc-named-color()', '': vtIsIdent, - '': p => vtIsIdent(p) && - !/^(span|auto|initial|inherit|unset|default)$/i.test(p.value), + '': p => vtIsIdent(p) && !isGlobalKeyword(p.value) && + !/^(span|auto|default)$/i.test(p.value), '': p => vtIsIdent(p) && !VTSimple[''](p), '': p => vtIsIdent(p) && !lowerCmp(p.value, 'none'), '': ' | | cross-fade()', @@ -793,8 +799,8 @@ self.parserlib = (() => { '': ' | margin-box', '': 'normal | reverse | alternate | alternate-reverse', '': 'none | forwards | backwards | both', - '': p => vtIsIdent(p) && - /^(?!(none|unset|initial|inherit)$)-?[a-z_][-a-z0-9_]+$/i.test(p), + '': p => vtIsIdent(p) && !isGlobalKeyword(p) && + /^-?[a-z_][-a-z0-9_]+$/i.test(p), '': p => p.type === 'string', '': 'start | end | left | right | center | justify | match-parent', '': 'solid | double | dotted | dashed | wavy', @@ -2395,10 +2401,8 @@ self.parserlib = (() => { const validationCache = new Map(); - function validateProperty(property, value) { - // Global keywords that can be set for any property are conveniently listed in `all` prop: - // https://drafts.csswg.org/css-cascade/#all-shorthand - if (/^(inherit|initial|unset|revert)$/i.test(value.parts[0])) { + function validateProperty(name, property, value) { + if (isGlobalKeyword(value.parts[0])) { if (value.parts.length > 1) { throwEndExpected(value.parts[1], true); } @@ -2407,7 +2411,7 @@ self.parserlib = (() => { if (hasVarParts(value)) { return; } - const prop = lower(property); + const prop = lower(name); let known = validationCache.get(prop); if (known && known.has(value.text)) { return; @@ -2417,7 +2421,7 @@ self.parserlib = (() => { return; } if (!spec) { - throw new ValidationError(`Unknown property '${property}'.`, value); + throw new ValidationError(`Unknown property '${name}'.`, value); } // Property-specific validation. const expr = new PropertyValueIterator(value); @@ -4066,7 +4070,7 @@ self.parserlib = (() => { this.options.underscoreHack && property.hack === '_' ? property.text : property.toString(); - validateProperty(name, value); + validateProperty(name, property, value); } catch (ex) { if (!(ex instanceof ValidationError)) { ex.message = ex.stack; @@ -4650,14 +4654,15 @@ self.parserlib = (() => { css: { Colors, Combinator, + GlobalKeywords, + Matcher, + MediaFeature, + MediaQuery, Parser, Properties, PropertyName, PropertyValue, PropertyValuePart, - Matcher, - MediaFeature, - MediaQuery, Selector, SelectorPart, SelectorSubPart,