|
| 1 | +# 5.0.0-rc.0 |
| 2 | + |
| 3 | +This release has **BREAKING CHANGES** that were required to fix regressions |
| 4 | +in 4.0.0 and to make the Combinator Node API consistent for all combinator |
| 5 | +types. Please read carefully. |
| 6 | + |
| 7 | +## Summary of Changes |
| 8 | + |
| 9 | +* The way a descendent combinator that isn't a single space character (E.g. `.a .b`) is stored in the AST has changed. |
| 10 | +* Named Combinators (E.g. `.a /for/ .b`) are now properly parsed as a combinator. |
| 11 | +* It is now possible to look up a node based on the source location of a character in that node and to query nodes if they contain some character. |
| 12 | +* Several bug fixes that caused the parser to hang and run out of memory when a `/` was encountered have been fixed. |
| 13 | +* The minimum supported version of Node is now `v6.0.0`. |
| 14 | + |
| 15 | +### Changes to the Descendent Combinator |
| 16 | + |
| 17 | +In prior releases, the value of a descendant combinator with multiple spaces included all the spaces. |
| 18 | + |
| 19 | +* `.a .b`: Extra spaces are now stored as space before. |
| 20 | + - Old & Busted: |
| 21 | + - `combinator.value === " "` |
| 22 | + - New hotness: |
| 23 | + - `combinator.value === " " && combinator.spaces.before === " "` |
| 24 | +* `.a /*comment*/.b`: A comment at the end of the combinator causes extra space to become after space. |
| 25 | + - Old & Busted: |
| 26 | + - `combinator.value === " "` |
| 27 | + - `combinator.raws.value === " /*comment/"` |
| 28 | + - New hotness: |
| 29 | + - `combinator.value === " "` |
| 30 | + - `combinator.spaces.after === " "` |
| 31 | + - `combinator.raws.spaces.after === " /*comment*/"` |
| 32 | +* `.a<newline>.b`: whitespace that doesn't start or end with a single space character is stored as a raw value. |
| 33 | + - Old & Busted: |
| 34 | + - `combinator.value === "\n"` |
| 35 | + - `combinator.raws.value === undefined` |
| 36 | + - New hotness: |
| 37 | + - `combinator.value === " "` |
| 38 | + - `combinator.raws.value === "\n"` |
| 39 | + |
| 40 | +### Support for "Named Combinators" |
| 41 | + |
| 42 | +Although, nonstandard and unlikely to ever become a standard, combinators like `/deep/` and `/for/` are now properly supported. |
| 43 | + |
| 44 | +Because they've been taken off the standardization track, there is no spec-official name for combinators of the form `/<ident>/`. However, I talked to [Tab Atkins](https://twitter.com/tabatkins) and we agreed to call them "named combinators" so now they are called that. |
| 45 | + |
| 46 | +Before this release such named combinators were parsed without intention and generated three nodes of type `"tag"` where the first and last nodes had a value of `"/"`. |
| 47 | + |
| 48 | +* `.a /for/ .b` is parsed as a combinator. |
| 49 | + - Old & Busted: |
| 50 | + - `root.nodes[0].nodes[1].type === "tag"` |
| 51 | + - `root.nodes[0].nodes[1].value === "/"` |
| 52 | + - New hotness: |
| 53 | + - `root.nodes[0].nodes[1].type === "combinator"` |
| 54 | + - `root.nodes[0].nodes[1].value === "/for/"` |
| 55 | +* `.a /F\6fR/ .b` escapes are handled and uppercase is normalized. |
| 56 | + - Old & Busted: |
| 57 | + - `root.nodes[0].nodes[2].type === "tag"` |
| 58 | + - `root.nodes[0].nodes[2].value === "F\\6fR"` |
| 59 | + - New hotness: |
| 60 | + - `root.nodes[0].nodes[1].type === "combinator"` |
| 61 | + - `root.nodes[0].nodes[1].value === "/for/"` |
| 62 | + - `root.nodes[0].nodes[1].raws.value === "/F\\6fR/"` |
| 63 | + |
| 64 | +### Source position checks and lookups |
| 65 | + |
| 66 | +A new API was added to look up a node based on the source location. |
| 67 | + |
| 68 | +```js |
| 69 | +const selectorParser = require("postcss-selector-parser"); |
| 70 | +// You can find the most specific node for any given character |
| 71 | +let combinator = selectorParser.astSync(".a > .b").atPosition(1,4); |
| 72 | +combinator.toString() === " > "; |
| 73 | +// You can check if a node includes a specific character |
| 74 | +// Whitespace surrounding the node that is owned by that node |
| 75 | +// is included in the check. |
| 76 | +[2,3,4,5,6].map(column => combinator.isAtPosition(1, column)); |
| 77 | +// => [false, true, true, true, false] |
| 78 | +``` |
| 79 | + |
| 80 | +# 4.0.0 |
| 81 | + |
| 82 | +This release has **BREAKING CHANGES** that were required to fix bugs regarding values with escape sequences. Please read carefully. |
| 83 | + |
| 84 | +* **Identifiers with escapes** - CSS escape sequences are now hidden from the public API by default. |
| 85 | + The normal value of a node like a class name or ID, or an aspect of a node such as attribute |
| 86 | + selector's value, is unescaped. Escapes representing Non-ascii characters are unescaped into |
| 87 | + unicode characters. For example: `bu\tton, .\31 00, #i\2764\FE0Fu, [attr="value is \"quoted\""]` |
| 88 | + will parse respectively to the values `button`, `100`, `i❤️u`, `value is "quoted"`. |
| 89 | + The original escape sequences for these values can be found in the corresponding property name |
| 90 | + in `node.raws`. Where possible, deprecation warnings were added, but the nature |
| 91 | + of escape handling makes it impossible to detect what is escaped or not. Our expectation is |
| 92 | + that most users are neither expecting nor handling escape sequences in their use of this library, |
| 93 | + and so for them, this is a bug fix. Users who are taking care to handle escapes correctly can |
| 94 | + now update their code to remove the escape handling and let us do it for them. |
| 95 | + |
| 96 | +* **Mutating values with escapes** - When you make an update to a node property that has escape handling |
| 97 | + The value is assumed to be unescaped, and any special characters are escaped automatically and |
| 98 | + the corresponding `raws` value is immediately updated. This can result in changes to the original |
| 99 | + escape format. Where the exact value of the escape sequence is important there are methods that |
| 100 | + allow both values to be set in conjunction. There are a number of new convenience methods for |
| 101 | + manipulating values that involve escapes, especially for attributes values where the quote mark |
| 102 | + is involved. See https://github.com/postcss/postcss-selector-parser/pull/133 for an extensive |
| 103 | + write-up on these changes. |
| 104 | + |
| 105 | + |
| 106 | +**Upgrade/API Example** |
| 107 | + |
| 108 | +In `3.x` there was no unescape handling and internal consistency of several properties was the caller's job to maintain. It was very easy for the developer |
| 109 | +to create a CSS file that did not parse correctly when some types of values |
| 110 | +were in use. |
| 111 | + |
| 112 | +```js |
| 113 | +const selectorParser = require("postcss-selector-parser"); |
| 114 | +let attr = selectorParser.attribute({attribute: "id", operator: "=", value: "a-value"}); |
| 115 | +attr.value; // => "a-value" |
| 116 | +attr.toString(); // => [id=a-value] |
| 117 | +// Add quotes to an attribute's value. |
| 118 | +// All these values have to be set by the caller to be consistent: |
| 119 | +// no internal consistency is maintained. |
| 120 | +attr.raws.unquoted = attr.value |
| 121 | +attr.value = "'" + attr.value + "'"; |
| 122 | +attr.value; // => "'a-value'" |
| 123 | +attr.quoted = true; |
| 124 | +attr.toString(); // => "[id='a-value']" |
| 125 | +``` |
| 126 | + |
| 127 | +In `4.0` there is a convenient API for setting and mutating values |
| 128 | +that may need escaping. Especially for attributes. |
| 129 | + |
| 130 | +```js |
| 131 | +const selectorParser = require("postcss-selector-parser"); |
| 132 | + |
| 133 | +// The constructor requires you specify the exact escape sequence |
| 134 | +let className = selectorParser.className({value: "illegal class name", raws: {value: "illegal\\ class\\ name"}}); |
| 135 | +className.toString(); // => '.illegal\\ class\\ name' |
| 136 | + |
| 137 | +// So it's better to set the value as a property |
| 138 | +className = selectorParser.className(); |
| 139 | +// Most properties that deal with identifiers work like this |
| 140 | +className.value = "escape for me"; |
| 141 | +className.value; // => 'escape for me' |
| 142 | +className.toString(); // => '.escape\\ for\\ me' |
| 143 | + |
| 144 | +// emoji and all non-ascii are escaped to ensure it works in every css file. |
| 145 | +className.value = "😱🦄😍"; |
| 146 | +className.value; // => '😱🦄😍' |
| 147 | +className.toString(); // => '.\\1F631\\1F984\\1F60D' |
| 148 | + |
| 149 | +// you can control the escape sequence if you want, or do bad bad things |
| 150 | +className.setPropertyAndEscape('value', 'xxxx', 'yyyy'); |
| 151 | +className.value; // => "xxxx" |
| 152 | +className.toString(); // => ".yyyy" |
| 153 | + |
| 154 | +// Pass a value directly through to the css output without escaping it. |
| 155 | +className.setPropertyWithoutEscape('value', '$REPLACE_ME$'); |
| 156 | +className.value; // => "$REPLACE_ME$" |
| 157 | +className.toString(); // => ".$REPLACE_ME$" |
| 158 | + |
| 159 | +// The biggest changes are to the Attribute class |
| 160 | +// passing quoteMark explicitly is required to avoid a deprecation warning. |
| 161 | +let attr = selectorParser.attribute({attribute: "id", operator: "=", value: "a-value", quoteMark: null}); |
| 162 | +attr.toString(); // => "[id=a-value]" |
| 163 | +// Get the value with quotes on it and any necessary escapes. |
| 164 | +// This is the same as reading attr.value in 3.x. |
| 165 | +attr.getQuotedValue(); // => "a-value"; |
| 166 | +attr.quoteMark; // => null |
| 167 | + |
| 168 | +// Add quotes to an attribute's value. |
| 169 | +attr.quoteMark = "'"; // This is all that's required. |
| 170 | +attr.toString(); // => "[id='a-value']" |
| 171 | +attr.quoted; // => true |
| 172 | +// The value is still the same, only the quotes have changed. |
| 173 | +attr.value; // => a-value |
| 174 | +attr.getQuotedValue(); // => "'a-value'"; |
| 175 | + |
| 176 | +// deprecated assignment, no warning because there's no escapes |
| 177 | +attr.value = "new-value"; |
| 178 | +// no quote mark is needed so it is removed |
| 179 | +attr.getQuotedValue(); // => "new-value"; |
| 180 | + |
| 181 | +// deprecated assignment, |
| 182 | +attr.value = "\"a 'single quoted' value\""; |
| 183 | +// > (node:27859) DeprecationWarning: Assigning an attribute a value containing characters that might need to be escaped is deprecated. Call attribute.setValue() instead. |
| 184 | +attr.getQuotedValue(); // => '"a \'single quoted\' value"'; |
| 185 | +// quote mark inferred from first and last characters. |
| 186 | +attr.quoteMark; // => '"' |
| 187 | + |
| 188 | +// setValue takes options to make manipulating the value simple. |
| 189 | +attr.setValue('foo', {smart: true}); |
| 190 | +// foo doesn't require any escapes or quotes. |
| 191 | +attr.toString(); // => '[id=foo]' |
| 192 | +attr.quoteMark; // => null |
| 193 | + |
| 194 | +// An explicit quote mark can be specified |
| 195 | +attr.setValue('foo', {quoteMark: '"'}); |
| 196 | +attr.toString(); // => '[id="foo"]' |
| 197 | + |
| 198 | +// preserves quote mark by default |
| 199 | +attr.setValue('bar'); |
| 200 | +attr.toString(); // => '[id="bar"]' |
| 201 | +attr.quoteMark = null; |
| 202 | +attr.toString(); // => '[id=bar]' |
| 203 | + |
| 204 | +// with no arguments, it preserves quote mark even when it's not a great idea |
| 205 | +attr.setValue('a value \n that should be quoted'); |
| 206 | +attr.toString(); // => '[id=a\\ value\\ \\A\\ that\\ should\\ be\\ quoted]' |
| 207 | + |
| 208 | +// smart preservation with a specified default |
| 209 | +attr.setValue('a value \n that should be quoted', {smart: true, preferCurrentQuoteMark: true, quoteMark: "'"}); |
| 210 | +// => "[id='a value \\A that should be quoted']" |
| 211 | +attr.quoteMark = '"'; |
| 212 | +// => '[id="a value \\A that should be quoted"]' |
| 213 | + |
| 214 | +// this keeps double quotes because it wants to quote the value and the existing value has double quotes. |
| 215 | +attr.setValue('this should be quoted', {smart: true, preferCurrentQuoteMark: true, quoteMark: "'"}); |
| 216 | +// => '[id="this should be quoted"]' |
| 217 | + |
| 218 | +// picks single quotes because the value has double quotes |
| 219 | +attr.setValue('a "double quoted" value', {smart: true, preferCurrentQuoteMark: true, quoteMark: "'"}); |
| 220 | +// => "[id='a "double quoted" value']" |
| 221 | + |
| 222 | +// setPropertyAndEscape lets you do anything you want. Even things that are a bad idea and illegal. |
| 223 | +attr.setPropertyAndEscape('value', 'xxxx', 'the password is 42'); |
| 224 | +attr.value; // => "xxxx" |
| 225 | +attr.toString(); // => "[id=the password is 42]" |
| 226 | + |
| 227 | +// Pass a value directly through to the css output without escaping it. |
| 228 | +attr.setPropertyWithoutEscape('value', '$REPLACEMENT$'); |
| 229 | +attr.value; // => "$REPLACEMENT$" |
| 230 | +attr.toString(); // => "[id=$REPLACEMENT$]" |
| 231 | +``` |
| 232 | + |
1 | 233 | # 3.1.2 |
2 | 234 |
|
3 | 235 | * Fix: Removed dot-prop dependency since it's no longer written in es5. |
|
0 commit comments