Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/Parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,10 @@ Node type mapping:

#### URL nodes

When css-tree produces a `Url` node, it is represented as a `Word` node whose `value` is the URL string. URLs inside `url()` appear as a `Func` node named `url`.
When css-tree produces a `Url` node, it is represented as a `Word` node whose `value` is the URL string. For these nodes:
Comment thread
shellscape marked this conversation as resolved.

- `isUrl` is `true`
- `isParseableUrl` reflects whether the URL string is parseable (via `is-url-superb`)

#### Fallback Behavior

Expand Down Expand Up @@ -125,6 +128,7 @@ The parser creates nodes using the NodeOptions interface:

```typescript
interface NodeOptions {
isUrl?: boolean; // Internal URL marker used when mapping Url nodes
Comment thread
shellscape marked this conversation as resolved.
Outdated
node?: CssNode; // Original css-tree node
value?: string; // String value
parent?: any; // Parent node
Expand Down
26 changes: 15 additions & 11 deletions docs/Word.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ If `true`, denotes that the word represents a hexadecimal value.

Type: `Boolean`<br>

If `true`, denotes that the word represents a Universal Resource Locator (URL). Note that this is only set to `true` for standalone URLs, not for URLs within function calls like `url()`.
If `true`, denotes that the word represents a URL value. This includes URL values that originate from `url(...)`.

### `isParseableUrl`

Type: `Boolean`<br>

If `true`, denotes that the word's value is recognized as a parseable URL by [`is-url-superb`](https://www.npmjs.com/package/is-url-superb).

### `isVariable`

Expand Down Expand Up @@ -52,19 +58,17 @@ The value of the word.

## URL Handling

The Word node has special handling for URLs that appear outside of function contexts. When a standalone URL is encountered in a CSS value, it is parsed as a Word node with the `isUrl` property set to `true`. This is different from URLs that appear within `url()` functions.
URL values are represented as `Word` nodes. Use `isUrl` to determine whether a `Word` is a URL value, and use `isParseableUrl` to determine whether the URL string itself is parseable.

```js
import { parse } from 'postcss-values-parser';

const root = parse('https://example.com');
const wordNode = root.nodes[0];

console.log(wordNode.type); // 'word'
console.log(wordNode.isUrl); // true
console.log(wordNode.value); // 'https://example.com'
```

Note: URLs within `url()` functions are handled differently and create `Func` nodes instead of `Word` nodes.
const absolute = parse('url(https://example.com/image.png)').nodes[0];
const relative = parse('url(/images/image.png)').nodes[0];

console.log(absolute.isUrl); // true
console.log(absolute.isParseableUrl); // true

console.log(relative.isUrl); // true
console.log(relative.isParseableUrl); // false
```
1 change: 1 addition & 0 deletions src/nodes/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Input, Node as PostCssNode } from 'postcss';
import { stringify } from '../stringify.js';

export interface NodeOptions {
isUrl?: boolean;
Comment thread
shellscape marked this conversation as resolved.
Outdated
node?: CssNode;
value?: string;
parent?: any;
Expand Down
8 changes: 7 additions & 1 deletion src/nodes/Word.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export class Word extends Node {
readonly isVariable: boolean = false;
declare type: string;

get isParseableUrl(): boolean {
return !this.isVariable && isUrl(this.value);
}

constructor(options: NodeOptions) {
Comment thread
shellscape marked this conversation as resolved.
Outdated
super(options);
this.type = 'word';
Expand Down Expand Up @@ -58,7 +62,9 @@ export class Word extends Node {
// Determine word properties
this.isHex = reHex.test(value);
this.isVariable = reVariable.test(value);
this.isUrl = !this.isVariable && isUrl(value);
const parseableUrl = !this.isVariable && isUrl(value);

this.isUrl = Boolean(options && options.isUrl) || parseableUrl;
Comment thread
shellscape marked this conversation as resolved.
Outdated
this.isColor = this.isHex || (colorNames as any)[value.toLowerCase()] !== undefined;
}
}
2 changes: 2 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const assign = (parent: Nodes.Container | Nodes.Root, nodes: CssNode[]) => {
case 'Url':
// Create a Word node for URL with the URL value for toString()
newNode = new Nodes.Word({
isUrl: true,
node: {
...node,
type: 'Identifier' as any,
Expand Down Expand Up @@ -183,6 +184,7 @@ export const parse = (css: string, _opts?: ParseOptions) => {
case 'Url':
// Create a Word node for URL with the URL value for toString()
newNode = new Nodes.Word({
isUrl: true,
node: {
...nodeOptions.node,
type: 'Identifier' as any,
Expand Down
16 changes: 16 additions & 0 deletions test/func.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,20 @@ describe('function parsing', () => {
expect(() => parse(fixture)).toThrow();
});
}

it('should mark absolute url() words as parseable URLs', () => {
const root = parse('url(https://example.com/image.png)');
Comment thread
shellscape marked this conversation as resolved.
Outdated
const node = root.first as any;

expect(node.isUrl).toBe(true);
expect(node.isParseableUrl).toBe(true);
});

it('should mark relative url() words as URL words but not parseable URLs', () => {
const root = parse('url(/images/image.png)');
Comment thread
shellscape marked this conversation as resolved.
Outdated
const node = root.first as any;

expect(node.isUrl).toBe(true);
expect(node.isParseableUrl).toBe(false);
});
});
14 changes: 7 additions & 7 deletions test/snapshots/func.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3730,7 +3730,7 @@ exports[`function parsing > should parse: url( "/gfx/img/bg.jpg" ) 3`] = `
],
"isColor": false,
"isHex": false,
"isUrl": false,
"isUrl": true,
"isVariable": false,
"raws": {},
"source": {
Expand Down Expand Up @@ -3806,7 +3806,7 @@ exports[`function parsing > should parse: url( '/gfx/img/bg.jpg' ) 3`] = `
],
"isColor": false,
"isHex": false,
"isUrl": false,
"isUrl": true,
"isVariable": false,
"raws": {},
"source": {
Expand Down Expand Up @@ -3882,7 +3882,7 @@ exports[`function parsing > should parse: url( /gfx/img/bg.jpg 3`] = `
],
"isColor": false,
"isHex": false,
"isUrl": false,
"isUrl": true,
"isVariable": false,
"raws": {},
"source": {
Expand Down Expand Up @@ -3920,7 +3920,7 @@ exports[`function parsing > should parse: url( /gfx/img/bg.jpg ) 3`] = `
],
"isColor": false,
"isHex": false,
"isUrl": false,
"isUrl": true,
"isVariable": false,
"raws": {},
"source": {
Expand Down Expand Up @@ -3958,7 +3958,7 @@ exports[`function parsing > should parse: url() 3`] = `
],
"isColor": false,
"isHex": false,
"isUrl": false,
"isUrl": true,
"isVariable": false,
"raws": {},
"source": {
Expand Down Expand Up @@ -3996,7 +3996,7 @@ exports[`function parsing > should parse: url() foo bar baz 3`] = `
],
"isColor": false,
"isHex": false,
"isUrl": false,
"isUrl": true,
"isVariable": false,
"raws": {},
"source": {
Expand Down Expand Up @@ -4121,7 +4121,7 @@ exports[`function parsing > should parse: url(//123.example.com) 3`] = `
],
"isColor": false,
"isHex": false,
"isUrl": false,
"isUrl": true,
"isVariable": false,
"raws": {},
"source": {
Expand Down
Loading