@plumeria/eslint-plugin 19.14.3 → 19.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -49,6 +49,7 @@ exports.noDestructure = {
49
49
  keyName === 'createTheme' ||
50
50
  keyName === 'keyframes' ||
51
51
  keyName === 'viewTransition' ||
52
+ keyName === 'varType' ||
52
53
  keyName === 'use') {
53
54
  context.report({
54
55
  node: prop,
@@ -80,7 +80,8 @@ exports.noInnerCall = {
80
80
  propertyName === 'createStatic' ||
81
81
  propertyName === 'createTheme' ||
82
82
  propertyName === 'keyframes' ||
83
- propertyName === 'viewTransition') {
83
+ propertyName === 'viewTransition' ||
84
+ propertyName === 'varType') {
84
85
  context.report({
85
86
  node,
86
87
  messageId: 'noInnerCall',
@@ -7,6 +7,7 @@ const STYLE_APIS = new Set([
7
7
  'createTheme',
8
8
  'keyframes',
9
9
  'viewTransition',
10
+ 'varType',
10
11
  ]);
11
12
  const isReference = (node, parent) => {
12
13
  if (!parent)
@@ -899,34 +899,35 @@ function getFoldedValues(key) {
899
899
  foldedValueCache.set(key, folded);
900
900
  return folded;
901
901
  }
902
+ const transformFunctionString = [
903
+ `matrix\\(${mathContentsString}\\)`,
904
+ `matrix3d\\(${mathContentsString}\\)`,
905
+ `perspective\\(${mathContentsString}\\)`,
906
+ `rotate\\(${mathContentsString}\\)`,
907
+ `rotate3d\\(${mathContentsString}\\)`,
908
+ `rotateX\\(${mathContentsString}\\)`,
909
+ `rotateY\\(${mathContentsString}\\)`,
910
+ `rotateZ\\(${mathContentsString}\\)`,
911
+ `scale\\(${mathContentsString}\\)`,
912
+ `scale3d\\(${mathContentsString}\\)`,
913
+ `scaleX\\(${mathContentsString}\\)`,
914
+ `scaleY\\(${mathContentsString}\\)`,
915
+ `scaleZ\\(${mathContentsString}\\)`,
916
+ `skew\\(${mathContentsString}\\)`,
917
+ `skewX\\(${mathContentsString}\\)`,
918
+ `skewY\\(${mathContentsString}\\)`,
919
+ `translate\\(${mathContentsString}\\)`,
920
+ `translate3d\\(${mathContentsString}\\)`,
921
+ `translateX\\(${mathContentsString}\\)`,
922
+ `translateY\\(${mathContentsString}\\)`,
923
+ `translateZ\\(${mathContentsString}\\)`,
924
+ ].join('|');
902
925
  function getValidator(key) {
903
926
  if (validatorCache.has(key))
904
927
  return validatorCache.get(key);
905
928
  let validator = null;
906
929
  const lvp = getLengthValuePattern(key);
907
- const getTransformFunctions = () => [
908
- `matrix\\(${mathContentsString}\\)`,
909
- `matrix3d\\(${mathContentsString}\\)`,
910
- `perspective\\(${mathContentsString}\\)`,
911
- `rotate\\(${mathContentsString}\\)`,
912
- `rotate3d\\(${mathContentsString}\\)`,
913
- `rotateX\\(${mathContentsString}\\)`,
914
- `rotateY\\(${mathContentsString}\\)`,
915
- `rotateZ\\(${mathContentsString}\\)`,
916
- `scale\\(${mathContentsString}\\)`,
917
- `scale3d\\(${mathContentsString}\\)`,
918
- `scaleX\\(${mathContentsString}\\)`,
919
- `scaleY\\(${mathContentsString}\\)`,
920
- `scaleZ\\(${mathContentsString}\\)`,
921
- `skew\\(${mathContentsString}\\)`,
922
- `skewX\\(${mathContentsString}\\)`,
923
- `skewY\\(${mathContentsString}\\)`,
924
- `translate\\(${mathContentsString}\\)`,
925
- `translate3d\\(${mathContentsString}\\)`,
926
- `translateX\\(${mathContentsString}\\)`,
927
- `translateY\\(${mathContentsString}\\)`,
928
- `translateZ\\(${mathContentsString}\\)`,
929
- ].join('|');
930
+ const getTransformFunctions = () => transformFunctionString;
930
931
  const getGeometryBaseSet = () => 'content-box|padding-box|border-box|fill-box|stroke-box|view-box';
931
932
  if (['translate'].includes(key)) {
932
933
  const r = new RegExp(`^(${lvp}|${percentagePattern})(\\s(${lvp}|${percentagePattern}))?(\\s(${lvp}))?$`);
@@ -1848,6 +1849,109 @@ function getValidator(key) {
1848
1849
  validatorCache.set(key, validator);
1849
1850
  return validator;
1850
1851
  }
1852
+ const contextDependentUnits = [
1853
+ 'cap',
1854
+ 'ch',
1855
+ 'em',
1856
+ 'ex',
1857
+ 'ic',
1858
+ 'lh',
1859
+ 'rcap',
1860
+ 'rch',
1861
+ 'rem',
1862
+ 'rex',
1863
+ 'ric',
1864
+ 'rlh',
1865
+ 'cqw',
1866
+ 'cqh',
1867
+ 'cqi',
1868
+ 'cqb',
1869
+ 'cqmin',
1870
+ 'cqmax',
1871
+ ];
1872
+ const contextDependentRegex = new RegExp(`(?:var\\(|[\\d.](?:${contextDependentUnits.join('|')})(?![a-zA-Z0-9_-]))`, 'i');
1873
+ const quotedStringRegex = new RegExp(stringString, 'g');
1874
+ const timeString = `${pureNumber}(?:ms|s)`;
1875
+ const resolutionString = `${pureNumber}(?:dpi|dpcm|dppx|x)`;
1876
+ const customIdentRegex = /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/;
1877
+ const exactly = (pattern) => {
1878
+ const r = new RegExp(`^(?:${pattern})$`);
1879
+ return (v) => r.test(v);
1880
+ };
1881
+ const varTypeValidators = {
1882
+ '<length>': exactly(`${lengthPattern}|${calcString}`),
1883
+ '<number>': exactly(`${pureNumber}|${calcString}`),
1884
+ '<integer>': exactly(`${pureInteger}|${calcString}`),
1885
+ '<percentage>': exactly(`${percentagePattern}|${calcString}`),
1886
+ '<length-percentage>': exactly(`${lengthPattern}|${percentagePattern}|${calcString}`),
1887
+ '<angle>': exactly(`${pureAngle}|${calcString}`),
1888
+ '<time>': exactly(`${timeString}|${calcString}`),
1889
+ '<resolution>': exactly(`${resolutionString}|${calcString}`),
1890
+ '<color>': isColor,
1891
+ '<image>': exactly(`${urlString}|${gradientString}|${imageSetString}`),
1892
+ '<url>': exactly(urlString),
1893
+ '<string>': exactly(stringString),
1894
+ '<custom-ident>': (v) => customIdentRegex.test(v) &&
1895
+ !globalValues.includes(v.toLowerCase()) &&
1896
+ v.toLowerCase() !== 'default',
1897
+ '<transform-function>': exactly(transformFunctionString),
1898
+ '<transform-list>': exactly(`(?:${transformFunctionString})(?:\\s+(?:${transformFunctionString}))*`),
1899
+ };
1900
+ function splitTopLevelSpaces(value) {
1901
+ const parts = [];
1902
+ let depth = 0;
1903
+ let current = '';
1904
+ for (const char of value) {
1905
+ if (char === '(')
1906
+ depth++;
1907
+ else if (char === ')')
1908
+ depth--;
1909
+ if (depth === 0 && /\s/.test(char)) {
1910
+ if (current)
1911
+ parts.push(current);
1912
+ current = '';
1913
+ }
1914
+ else {
1915
+ current += char;
1916
+ }
1917
+ }
1918
+ if (current)
1919
+ parts.push(current);
1920
+ return parts;
1921
+ }
1922
+ function matchesSyntaxComponent(component, value) {
1923
+ const multiplier = /[+#]$/.test(component) ? component.slice(-1) : '';
1924
+ const name = multiplier ? component.slice(0, -1) : component;
1925
+ if (!name.startsWith('<'))
1926
+ return multiplier ? null : value === name;
1927
+ const validator = varTypeValidators[name];
1928
+ if (!validator)
1929
+ return null;
1930
+ if (!multiplier)
1931
+ return validator(value);
1932
+ const items = multiplier === '+'
1933
+ ? splitTopLevelSpaces(value)
1934
+ : splitTopLevelCommas(value);
1935
+ return items.every((item) => item !== '' && validator(item));
1936
+ }
1937
+ function checkInitialValue(syntax, value) {
1938
+ const trimmedSyntax = syntax.trim();
1939
+ if (trimmedSyntax === '*')
1940
+ return null;
1941
+ if (contextDependentRegex.test(value.replace(quotedStringRegex, ''))) {
1942
+ return 'varTypeDependent';
1943
+ }
1944
+ const trimmedValue = value.trim();
1945
+ let unknown = false;
1946
+ for (const component of trimmedSyntax.split('|')) {
1947
+ const result = matchesSyntaxComponent(component.trim(), trimmedValue);
1948
+ if (result)
1949
+ return null;
1950
+ if (result === null)
1951
+ unknown = true;
1952
+ }
1953
+ return unknown ? null : 'varTypeMismatch';
1954
+ }
1851
1955
  exports.validateValues = {
1852
1956
  meta: {
1853
1957
  type: 'problem',
@@ -1858,6 +1962,8 @@ exports.validateValues = {
1858
1962
  validateValue: "'{{key}}' has an invalid value '{{value}}'. Valid values: {{validValues}}",
1859
1963
  invalidPrimitive: "'{{key}}' cannot be assigned a {{type}} value ({{value}}). CSS properties require string or number values.",
1860
1964
  invalidNumber: "'{{key}}' does not accept numeric values. Expected a string value.",
1965
+ varTypeMismatch: "css.varType initialValue '{{value}}' does not match the syntax '{{syntax}}'.",
1966
+ varTypeDependent: "css.varType initialValue '{{value}}' depends on the font, a container or var(), so the browser drops the @property. Use a value that does not depend on where it is used.",
1861
1967
  },
1862
1968
  schema: [],
1863
1969
  },
@@ -1947,6 +2053,43 @@ exports.validateValues = {
1947
2053
  }
1948
2054
  });
1949
2055
  }
2056
+ function checkVarType(arg) {
2057
+ if (!arg || arg.type !== 'ObjectExpression')
2058
+ return;
2059
+ let syntax = '*';
2060
+ let initialValue = null;
2061
+ for (const prop of arg.properties) {
2062
+ if (prop.type !== 'Property' ||
2063
+ prop.computed ||
2064
+ prop.key.type !== 'Identifier') {
2065
+ continue;
2066
+ }
2067
+ if (prop.key.name === 'syntax') {
2068
+ if (prop.value.type !== 'Literal')
2069
+ return;
2070
+ if (typeof prop.value.value !== 'string')
2071
+ return;
2072
+ syntax = prop.value.value;
2073
+ }
2074
+ else if (prop.key.name === 'initialValue') {
2075
+ initialValue = prop.value;
2076
+ }
2077
+ }
2078
+ if (!initialValue || initialValue.type !== 'Literal')
2079
+ return;
2080
+ const raw = initialValue.value;
2081
+ if (typeof raw !== 'string' && typeof raw !== 'number')
2082
+ return;
2083
+ const value = String(raw);
2084
+ const messageId = checkInitialValue(syntax, value);
2085
+ if (messageId) {
2086
+ context.report({
2087
+ node: initialValue,
2088
+ messageId,
2089
+ data: { value, syntax },
2090
+ });
2091
+ }
2092
+ }
1950
2093
  return {
1951
2094
  ImportDeclaration(node) {
1952
2095
  if (node.source.value === '@plumeria/core') {
@@ -1966,6 +2109,19 @@ exports.validateValues = {
1966
2109
  }
1967
2110
  },
1968
2111
  CallExpression(node) {
2112
+ const calleeName = node.callee.type === 'MemberExpression'
2113
+ ? node.callee.object.type === 'Identifier' &&
2114
+ plumeriaAliases[node.callee.object.name] === 'NAMESPACE' &&
2115
+ node.callee.property.type === 'Identifier'
2116
+ ? node.callee.property.name
2117
+ : null
2118
+ : node.callee.type === 'Identifier'
2119
+ ? plumeriaAliases[node.callee.name]
2120
+ : null;
2121
+ if (calleeName === 'varType') {
2122
+ checkVarType(node.arguments[0]);
2123
+ return;
2124
+ }
1969
2125
  let isCssProperties = false;
1970
2126
  if (node.callee.type === 'MemberExpression') {
1971
2127
  if (node.callee.object.type === 'Identifier' &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plumeria/eslint-plugin",
3
- "version": "19.14.3",
3
+ "version": "19.15.0",
4
4
  "description": "Plumeria ESLint plugin",
5
5
  "author": "Refirst 11",
6
6
  "license": "MIT",
@@ -59,7 +59,7 @@
59
59
  "dependencies": {
60
60
  "oxlint": "1.87.0",
61
61
  "zss-engine": "2.10.0",
62
- "@plumeria/compiler": "19.14.3"
62
+ "@plumeria/compiler": "19.15.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "rimraf dist && pnpm cjs",