generic.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. 'use strict';
  2. var names = require('../utils/names.js');
  3. // https://www.w3.org/TR/css-values-3/#lengths
  4. var LENGTH = {
  5. // absolute length units
  6. 'px': true,
  7. 'mm': true,
  8. 'cm': true,
  9. 'in': true,
  10. 'pt': true,
  11. 'pc': true,
  12. 'q': true,
  13. // relative length units
  14. 'em': true,
  15. 'ex': true,
  16. 'ch': true,
  17. 'rem': true,
  18. // viewport-percentage lengths
  19. 'vh': true,
  20. 'vw': true,
  21. 'vmin': true,
  22. 'vmax': true,
  23. 'vm': true
  24. };
  25. var ANGLE = {
  26. 'deg': true,
  27. 'grad': true,
  28. 'rad': true,
  29. 'turn': true
  30. };
  31. var TIME = {
  32. 's': true,
  33. 'ms': true
  34. };
  35. var FREQUENCY = {
  36. 'hz': true,
  37. 'khz': true
  38. };
  39. // https://www.w3.org/TR/css-values-3/#resolution (https://drafts.csswg.org/css-values/#resolution)
  40. var RESOLUTION = {
  41. 'dpi': true,
  42. 'dpcm': true,
  43. 'dppx': true,
  44. 'x': true // https://github.com/w3c/csswg-drafts/issues/461
  45. };
  46. // https://drafts.csswg.org/css-grid/#fr-unit
  47. var FLEX = {
  48. 'fr': true
  49. };
  50. // https://www.w3.org/TR/css3-speech/#mixing-props-voice-volume
  51. var DECIBEL = {
  52. 'db': true
  53. };
  54. // https://www.w3.org/TR/css3-speech/#voice-props-voice-pitch
  55. var SEMITONES = {
  56. 'st': true
  57. };
  58. // can be used wherever <length>, <frequency>, <angle>, <time>, <percentage>, <number>, or <integer> values are allowed
  59. // https://drafts.csswg.org/css-values/#calc-notation
  60. function isCalc(node) {
  61. if (node.data.type !== 'Function') {
  62. return false;
  63. }
  64. var keyword = names.keyword(node.data.name);
  65. // check the function name
  66. return (
  67. keyword.name === 'calc' ||
  68. keyword.name === '-moz-calc' ||
  69. keyword.name === '-webkit-calc'
  70. );
  71. }
  72. function astNode(type) {
  73. return function(node) {
  74. return node.data.type === type;
  75. };
  76. }
  77. function dimension(type) {
  78. return function(node) {
  79. return isCalc(node) ||
  80. (node.data.type === 'Dimension' && type.hasOwnProperty(node.data.unit.toLowerCase()));
  81. };
  82. }
  83. function zeroUnitlessDimension(type) {
  84. return function(node) {
  85. return isCalc(node) ||
  86. (node.data.type === 'Dimension' && type.hasOwnProperty(node.data.unit.toLowerCase())) ||
  87. (node.data.type === 'Number' && Number(node.data.value) === 0);
  88. };
  89. }
  90. function attr(node) {
  91. return node.data.type === 'Function' && node.data.name.toLowerCase() === 'attr';
  92. }
  93. function number(node) {
  94. return isCalc(node) || node.data.type === 'Number';
  95. }
  96. function numberZeroOne(node) {
  97. if (isCalc(node) || node.data.type === 'Number') {
  98. var value = Number(node.data.value);
  99. return value >= 0 && value <= 1;
  100. }
  101. return false;
  102. }
  103. function numberOneOrGreater(node) {
  104. if (isCalc(node) || node.data.type === 'Number') {
  105. return Number(node.data.value) >= 1;
  106. }
  107. return false;
  108. }
  109. // TODO: fail on 10e-2
  110. function integer(node) {
  111. return isCalc(node) ||
  112. (node.data.type === 'Number' && node.data.value.indexOf('.') === -1);
  113. }
  114. // TODO: fail on 10e-2
  115. function positiveInteger(node) {
  116. return isCalc(node) ||
  117. (node.data.type === 'Number' && node.data.value.indexOf('.') === -1 && node.data.value.charAt(0) !== '-');
  118. }
  119. function percentage(node) {
  120. return isCalc(node) ||
  121. node.data.type === 'Percentage';
  122. }
  123. function hexColor(node) {
  124. if (node.data.type !== 'HexColor') {
  125. return false;
  126. }
  127. var hex = node.data.value;
  128. return /^[0-9a-fA-F]{3,8}$/.test(hex) &&
  129. (hex.length === 3 || hex.length === 4 || hex.length === 6 || hex.length === 8);
  130. }
  131. function expression(node) {
  132. return node.data.type === 'Function' && node.data.name.toLowerCase() === 'expression';
  133. }
  134. // https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident
  135. // https://drafts.csswg.org/css-values-4/#identifier-value
  136. function customIdent(node) {
  137. if (node.data.type !== 'Identifier') {
  138. return false;
  139. }
  140. var name = node.data.name.toLowerCase();
  141. // § 3.2. Author-defined Identifiers: the <custom-ident> type
  142. // The CSS-wide keywords are not valid <custom-ident>s
  143. if (name === 'unset' || name === 'initial' || name === 'inherit') {
  144. return false;
  145. }
  146. // The default keyword is reserved and is also not a valid <custom-ident>
  147. if (name === 'default') {
  148. return false;
  149. }
  150. // TODO: ignore property specific keywords (as described https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident)
  151. return true;
  152. }
  153. module.exports = {
  154. 'angle': zeroUnitlessDimension(ANGLE),
  155. 'attr()': attr,
  156. 'custom-ident': customIdent,
  157. 'decibel': dimension(DECIBEL),
  158. 'dimension': astNode('Dimension'),
  159. 'frequency': dimension(FREQUENCY),
  160. 'flex': dimension(FLEX),
  161. 'hex-color': hexColor,
  162. 'id-selector': astNode('IdSelector'), // element( <id-selector> )
  163. 'ident': astNode('Identifier'),
  164. 'integer': integer,
  165. 'length': zeroUnitlessDimension(LENGTH),
  166. 'number': number,
  167. 'number-zero-one': numberZeroOne,
  168. 'number-one-or-greater': numberOneOrGreater,
  169. 'percentage': percentage,
  170. 'positive-integer': positiveInteger,
  171. 'resolution': dimension(RESOLUTION),
  172. 'semitones': dimension(SEMITONES),
  173. 'string': astNode('String'),
  174. 'time': dimension(TIME),
  175. 'unicode-range': astNode('UnicodeRange'),
  176. 'url': astNode('Url'),
  177. // old IE stuff
  178. 'progid': astNode('Raw'),
  179. 'expression': expression
  180. };