diff --git a/src/parse-atrule-prelude.ts b/src/parse-atrule-prelude.ts index 7866e18..31488b8 100644 --- a/src/parse-atrule-prelude.ts +++ b/src/parse-atrule-prelude.ts @@ -97,6 +97,16 @@ export class AtRulePreludeParser { return nodes } + private create_node(type: number, start: number, end: number): number { + let node = this.arena.create_node() + this.arena.set_type(node, type) + this.arena.set_start_offset(node, start) + this.arena.set_length(node, end - start) + this.arena.set_start_line(node, this.lexer.token_line) + this.arena.set_start_column(node, this.lexer.token_column) + return node + } + private is_and_or_not(str: string): boolean { if (str.length > 3 || str.length < 2) return false return str_equals('and', str) || str_equals('or', str) || str_equals('not', str) @@ -152,20 +162,11 @@ export class AtRulePreludeParser { if (this.is_and_or_not(text)) { // Logical operator - let op = this.arena.create_node() - this.arena.set_type(op, NODE_PRELUDE_OPERATOR) - this.arena.set_start_offset(op, this.lexer.token_start) - this.arena.set_length(op, this.lexer.token_end - this.lexer.token_start) - this.arena.set_start_line(op, this.lexer.token_line) + let op = this.create_node(NODE_PRELUDE_OPERATOR, this.lexer.token_start, this.lexer.token_end) components.push(op) } else { // Media type: screen, print, all - let media_type = this.arena.create_node() - this.arena.set_type(media_type, NODE_PRELUDE_MEDIA_TYPE) - this.arena.set_start_offset(media_type, this.lexer.token_start) - this.arena.set_length(media_type, this.lexer.token_end - this.lexer.token_start) - this.arena.set_start_line(media_type, this.lexer.token_line) - this.arena.set_start_column(media_type, this.lexer.token_column) + let media_type = this.create_node(NODE_PRELUDE_MEDIA_TYPE, this.lexer.token_start, this.lexer.token_end) components.push(media_type) } } else { @@ -177,11 +178,7 @@ export class AtRulePreludeParser { if (components.length === 0) return null // Create media query node - let query_node = this.arena.create_node() - this.arena.set_type(query_node, NODE_PRELUDE_MEDIA_QUERY) - this.arena.set_start_offset(query_node, query_start) - this.arena.set_length(query_node, this.lexer.pos - query_start) - this.arena.set_start_line(query_node, query_line) + let query_node = this.create_node(NODE_PRELUDE_MEDIA_QUERY, query_start, this.lexer.pos) // Append components as children for (let component of components) { @@ -216,11 +213,7 @@ export class AtRulePreludeParser { let feature_end = this.lexer.token_end // After ')' // Create media feature node - let feature = this.arena.create_node() - this.arena.set_type(feature, NODE_PRELUDE_MEDIA_FEATURE) - this.arena.set_start_offset(feature, feature_start) - this.arena.set_length(feature, feature_end - feature_start) - this.arena.set_start_line(feature, feature_line) + let feature = this.create_node(NODE_PRELUDE_MEDIA_FEATURE, feature_start, feature_end) // Store feature content (without parentheses) in value fields, trimmed let trimmed = trim_boundaries(this.source, content_start, content_end) @@ -261,19 +254,11 @@ export class AtRulePreludeParser { if (this.is_and_or_not(text)) { // Logical operator - let op = this.arena.create_node() - this.arena.set_type(op, NODE_PRELUDE_OPERATOR) - this.arena.set_start_offset(op, this.lexer.token_start) - this.arena.set_length(op, this.lexer.token_end - this.lexer.token_start) - this.arena.set_start_line(op, this.lexer.token_line) + let op = this.create_node(NODE_PRELUDE_OPERATOR, this.lexer.token_start, this.lexer.token_end) components.push(op) } else { // Container name or other identifier - let name = this.arena.create_node() - this.arena.set_type(name, NODE_PRELUDE_IDENTIFIER) - this.arena.set_start_offset(name, this.lexer.token_start) - this.arena.set_length(name, this.lexer.token_end - this.lexer.token_start) - this.arena.set_start_line(name, this.lexer.token_line) + let name = this.create_node(NODE_PRELUDE_IDENTIFIER, this.lexer.token_start, this.lexer.token_end) components.push(name) } } @@ -282,11 +267,7 @@ export class AtRulePreludeParser { if (components.length === 0) return [] // Create container query node - let query_node = this.arena.create_node() - this.arena.set_type(query_node, NODE_PRELUDE_CONTAINER_QUERY) - this.arena.set_start_offset(query_node, query_start) - this.arena.set_length(query_node, this.lexer.pos - query_start) - this.arena.set_start_line(query_node, query_line) + let query_node = this.create_node(NODE_PRELUDE_CONTAINER_QUERY, query_start, this.lexer.pos) // Append components as children for (let component of components) { @@ -332,11 +313,7 @@ export class AtRulePreludeParser { let feature_end = this.lexer.token_end // Create supports query node - let query = this.arena.create_node() - this.arena.set_type(query, NODE_PRELUDE_SUPPORTS_QUERY) - this.arena.set_start_offset(query, feature_start) - this.arena.set_length(query, feature_end - feature_start) - this.arena.set_start_line(query, feature_line) + let query = this.create_node(NODE_PRELUDE_SUPPORTS_QUERY, feature_start, feature_end) // Store query content in value fields, trimmed let trimmed = trim_boundaries(this.source, content_start, content_end) @@ -353,11 +330,7 @@ export class AtRulePreludeParser { let text = this.source.substring(this.lexer.token_start, this.lexer.token_end) if (this.is_and_or_not(text)) { - let op = this.arena.create_node() - this.arena.set_type(op, NODE_PRELUDE_OPERATOR) - this.arena.set_start_offset(op, this.lexer.token_start) - this.arena.set_length(op, this.lexer.token_end - this.lexer.token_start) - this.arena.set_start_line(op, this.lexer.token_line) + let op = this.create_node(NODE_PRELUDE_OPERATOR, this.lexer.token_start, this.lexer.token_end) nodes.push(op) } } @@ -379,11 +352,7 @@ export class AtRulePreludeParser { let token_type = this.lexer.token_type if (token_type === TOKEN_IDENT) { // Layer name - let layer = this.arena.create_node() - this.arena.set_type(layer, NODE_PRELUDE_LAYER_NAME) - this.arena.set_start_offset(layer, this.lexer.token_start) - this.arena.set_length(layer, this.lexer.token_end - this.lexer.token_start) - this.arena.set_start_line(layer, this.lexer.token_line) + let layer = this.create_node(NODE_PRELUDE_LAYER_NAME, this.lexer.token_start, this.lexer.token_end) nodes.push(layer) } else if (token_type === TOKEN_COMMA) { // Skip comma separator @@ -407,11 +376,7 @@ export class AtRulePreludeParser { if (this.lexer.token_type !== TOKEN_IDENT) return [] // Create identifier node - let ident = this.arena.create_node() - this.arena.set_type(ident, NODE_PRELUDE_IDENTIFIER) - this.arena.set_start_offset(ident, this.lexer.token_start) - this.arena.set_length(ident, this.lexer.token_end - this.lexer.token_start) - this.arena.set_start_line(ident, this.lexer.token_line) + let ident = this.create_node(NODE_PRELUDE_IDENTIFIER, this.lexer.token_start, this.lexer.token_end) return [ident] } @@ -494,12 +459,7 @@ export class AtRulePreludeParser { } // Create URL node - let url_node = this.arena.create_node() - this.arena.set_type(url_node, NODE_PRELUDE_IMPORT_URL) - this.arena.set_start_offset(url_node, url_start) - this.arena.set_length(url_node, url_end - url_start) - this.arena.set_start_line(url_node, url_line) - + let url_node = this.create_node(NODE_PRELUDE_IMPORT_URL, url_start, url_end) return url_node } @@ -547,11 +507,7 @@ export class AtRulePreludeParser { } // Create layer node - let layer_node = this.arena.create_node() - this.arena.set_type(layer_node, NODE_PRELUDE_IMPORT_LAYER) - this.arena.set_start_offset(layer_node, layer_start) - this.arena.set_length(layer_node, layer_end - layer_start) - this.arena.set_start_line(layer_node, layer_line) + let layer_node = this.create_node(NODE_PRELUDE_IMPORT_LAYER, layer_start, layer_end) // Store the layer name (content inside parentheses), trimmed if (content_length > 0) { @@ -604,11 +560,7 @@ export class AtRulePreludeParser { } // Create supports node - let supports_node = this.arena.create_node() - this.arena.set_type(supports_node, NODE_PRELUDE_IMPORT_SUPPORTS) - this.arena.set_start_offset(supports_node, supports_start) - this.arena.set_length(supports_node, supports_end - supports_start) - this.arena.set_start_line(supports_node, supports_line) + let supports_node = this.create_node(NODE_PRELUDE_IMPORT_SUPPORTS, supports_start, supports_end) return supports_node } diff --git a/src/parse-selector.test.ts b/src/parse-selector.test.ts index c52c2c9..39d15e3 100644 --- a/src/parse-selector.test.ts +++ b/src/parse-selector.test.ts @@ -48,7 +48,7 @@ describe('parse_selector() function', () => { const classNode = firstSelector?.first_child expect(classNode?.type).toBe(NODE_SELECTOR_CLASS) - expect(classNode?.name).toBe('my-class') + expect(classNode?.name).toBe('.my-class') }) it('should parse ID selector', () => { @@ -57,7 +57,7 @@ describe('parse_selector() function', () => { const idNode = firstSelector?.first_child expect(idNode?.type).toBe(NODE_SELECTOR_ID) - expect(idNode?.name).toBe('my-id') + expect(idNode?.name).toBe('#my-id') }) it('should parse compound selector', () => { @@ -167,7 +167,7 @@ describe('SelectorParser', () => { const child = arena.get_first_child(selectorWrapper) expect(arena.get_type(child)).toBe(NODE_SELECTOR_CLASS) expect(getNodeText(arena, source, child)).toBe('.my-class') - expect(getNodeContent(arena, source, child)).toBe('my-class') + expect(getNodeContent(arena, source, child)).toBe('.my-class') }) it('should parse ID selector', () => { @@ -185,7 +185,7 @@ describe('SelectorParser', () => { const child = arena.get_first_child(selectorWrapper) expect(arena.get_type(child)).toBe(NODE_SELECTOR_ID) expect(getNodeText(arena, source, child)).toBe('#my-id') - expect(getNodeContent(arena, source, child)).toBe('my-id') + expect(getNodeContent(arena, source, child)).toBe('#my-id') }) it('should parse universal selector', () => { @@ -243,7 +243,7 @@ describe('SelectorParser', () => { expect(arena.get_type(children[0])).toBe(NODE_SELECTOR_TYPE) expect(getNodeText(arena, source, children[0])).toBe('div') expect(arena.get_type(children[1])).toBe(NODE_SELECTOR_CLASS) - expect(getNodeContent(arena, source, children[1])).toBe('container') + expect(getNodeContent(arena, source, children[1])).toBe('.container') }) it('should parse element with ID', () => { @@ -260,7 +260,7 @@ describe('SelectorParser', () => { expect(children).toHaveLength(2) expect(arena.get_type(children[0])).toBe(NODE_SELECTOR_TYPE) expect(arena.get_type(children[1])).toBe(NODE_SELECTOR_ID) - expect(getNodeContent(arena, source, children[1])).toBe('app') + expect(getNodeContent(arena, source, children[1])).toBe('#app') }) it('should parse element with multiple classes', () => { @@ -274,11 +274,11 @@ describe('SelectorParser', () => { expect(children).toHaveLength(4) expect(arena.get_type(children[0])).toBe(NODE_SELECTOR_TYPE) expect(arena.get_type(children[1])).toBe(NODE_SELECTOR_CLASS) - expect(getNodeContent(arena, source, children[1])).toBe('foo') + expect(getNodeContent(arena, source, children[1])).toBe('.foo') expect(arena.get_type(children[2])).toBe(NODE_SELECTOR_CLASS) - expect(getNodeContent(arena, source, children[2])).toBe('bar') + expect(getNodeContent(arena, source, children[2])).toBe('.bar') expect(arena.get_type(children[3])).toBe(NODE_SELECTOR_CLASS) - expect(getNodeContent(arena, source, children[3])).toBe('baz') + expect(getNodeContent(arena, source, children[3])).toBe('.baz') }) it('should parse complex compound selector', () => { @@ -293,9 +293,9 @@ describe('SelectorParser', () => { expect(arena.get_type(children[0])).toBe(NODE_SELECTOR_TYPE) expect(getNodeText(arena, source, children[0])).toBe('div') expect(arena.get_type(children[1])).toBe(NODE_SELECTOR_CLASS) - expect(getNodeContent(arena, source, children[1])).toBe('container') + expect(getNodeContent(arena, source, children[1])).toBe('.container') expect(arena.get_type(children[2])).toBe(NODE_SELECTOR_ID) - expect(getNodeContent(arena, source, children[2])).toBe('app') + expect(getNodeContent(arena, source, children[2])).toBe('#app') }) }) @@ -634,7 +634,7 @@ describe('SelectorParser', () => { }) it('should parse attribute with uppercase case-insensitive flag', () => { - const { arena, rootNode, source } = parseSelectorInternal('[type="text" I]') + const { arena, rootNode } = parseSelectorInternal('[type="text" I]') expect(rootNode).not.toBeNull() if (!rootNode) return @@ -646,7 +646,7 @@ describe('SelectorParser', () => { }) it('should parse attribute with whitespace before flag', () => { - const { arena, rootNode, source } = parseSelectorInternal('[type="text" i]') + const { arena, rootNode } = parseSelectorInternal('[type="text" i]') expect(rootNode).not.toBeNull() if (!rootNode) return @@ -658,7 +658,7 @@ describe('SelectorParser', () => { }) it('should parse attribute without flag', () => { - const { arena, rootNode, source } = parseSelectorInternal('[type="text"]') + const { arena, rootNode } = parseSelectorInternal('[type="text"]') expect(rootNode).not.toBeNull() if (!rootNode) return @@ -1274,7 +1274,7 @@ describe('SelectorParser', () => { const child = arena.get_first_child(selectorWrapper) expect(arena.get_type(child)).toBe(NODE_SELECTOR_CLASS) - expect(getNodeContent(arena, source, child)).toBe('my-class-123') + expect(getNodeContent(arena, source, child)).toBe('.my-class-123') }) it('should parse hyphenated element names', () => { @@ -1310,7 +1310,7 @@ describe('SelectorParser', () => { const child = arena.get_first_child(selectorWrapper) expect(arena.get_type(child)).toBe(NODE_SELECTOR_CLASS) - expect(getNodeContent(arena, source, child)).toBe('block__element--modifier') + expect(getNodeContent(arena, source, child)).toBe('.block__element--modifier') }) it('should parse Bootstrap-style selector', () => { diff --git a/src/parse-selector.ts b/src/parse-selector.ts index 2dc7a1d..f6d88fe 100644 --- a/src/parse-selector.ts +++ b/src/parse-selector.ts @@ -103,18 +103,13 @@ export class SelectorParser { while (this.lexer.pos < this.selector_end) { let selector_start = this.lexer.pos - let selector_line = this.lexer.line - let selector_column = this.lexer.column + // let selector_line = this.lexer.line + // let selector_column = this.lexer.column let complex_selector = this.parse_complex_selector(allow_relative) if (complex_selector !== null) { // Wrap the complex selector (chain of components) in a NODE_SELECTOR - let selector_wrapper = this.arena.create_node() - this.arena.set_type(selector_wrapper, NODE_SELECTOR) - this.arena.set_start_offset(selector_wrapper, selector_start) - this.arena.set_length(selector_wrapper, this.lexer.pos - selector_start) - this.arena.set_start_line(selector_wrapper, selector_line) - this.arena.set_start_column(selector_wrapper, selector_column) + let selector_wrapper = this.create_node(NODE_SELECTOR, selector_start, this.lexer.pos) // Find the last component in the chain let last_component = complex_selector @@ -189,7 +184,7 @@ export class SelectorParser { let ch = this.source.charCodeAt(this.lexer.token_start) if (ch === CHAR_GREATER_THAN || ch === CHAR_PLUS || ch === CHAR_TILDE) { // Found leading combinator (>, +, ~) - this is a relative selector - let combinator = this.create_combinator(this.lexer.token_start, this.lexer.token_end) + let combinator = this.create_node(NODE_SELECTOR_COMBINATOR, this.lexer.token_start, this.lexer.token_end) components.push(combinator) this.skip_whitespace() // Continue to parse the rest normally @@ -302,11 +297,11 @@ export class SelectorParser { switch (token_type) { case TOKEN_IDENT: // Type selector: div, span, p - return this.create_type_selector(start, end) + return this.create_node(NODE_SELECTOR_TYPE, start, end) case TOKEN_HASH: // ID selector: #id - return this.create_id_selector(start, end) + return this.create_node(NODE_SELECTOR_ID, start, end) case TOKEN_DELIM: // Could be: . (class), * (universal), & (nesting) @@ -316,10 +311,10 @@ export class SelectorParser { return this.parse_class_selector(start) } else if (ch === CHAR_ASTERISK) { // * - universal selector - return this.create_universal_selector(start, end) + return this.create_node(NODE_SELECTOR_UNIVERSAL, start, end) } else if (ch === CHAR_AMPERSAND) { // & - nesting selector - return this.create_nesting_selector(start, end) + return this.create_node(NODE_SELECTOR_NESTING, start, end) } // Other delimiters signal end of selector return null @@ -371,7 +366,7 @@ export class SelectorParser { let ch = this.source.charCodeAt(this.lexer.token_start) if (is_combinator(ch)) { // > + ~ (combinator text excludes leading whitespace) - return this.create_combinator(this.lexer.token_start, this.lexer.token_end) + return this.create_node(NODE_SELECTOR_COMBINATOR, this.lexer.token_start, this.lexer.token_end) } } @@ -387,7 +382,7 @@ export class SelectorParser { break } } - return this.create_combinator(whitespace_start, this.lexer.pos) + return this.create_node(NODE_SELECTOR_COMBINATOR, whitespace_start, this.lexer.pos) } // No combinator found, reset position @@ -408,16 +403,7 @@ export class SelectorParser { return null } - let node = this.arena.create_node() - this.arena.set_type(node, NODE_SELECTOR_CLASS) - this.arena.set_start_offset(node, dot_pos) - this.arena.set_length(node, this.lexer.token_end - dot_pos) - this.arena.set_start_line(node, this.lexer.line) - this.arena.set_start_column(node, this.lexer.column) - // Content is the class name (without the dot) - this.arena.set_content_start(node, this.lexer.token_start) - this.arena.set_content_length(node, this.lexer.token_end - this.lexer.token_start) - return node + return this.create_node(NODE_SELECTOR_CLASS, dot_pos, this.lexer.token_end) } // Parse attribute selector ([attr], [attr=value], etc.) @@ -443,12 +429,7 @@ export class SelectorParser { } } - let node = this.arena.create_node() - this.arena.set_type(node, NODE_SELECTOR_ATTRIBUTE) - this.arena.set_start_offset(node, start) - this.arena.set_length(node, end - start) - this.arena.set_start_line(node, this.lexer.line) - this.arena.set_start_column(node, this.lexer.column) + let node = this.create_node(NODE_SELECTOR_ATTRIBUTE, start, end) // Parse the content inside brackets to extract name, operator, and value this.parse_attribute_content(node, content_start, content_end) @@ -627,12 +608,12 @@ export class SelectorParser { let token_type = this.lexer.token_type if (token_type === TOKEN_IDENT) { - let node = this.arena.create_node() - this.arena.set_type(node, is_pseudo_element ? NODE_SELECTOR_PSEUDO_ELEMENT : NODE_SELECTOR_PSEUDO_CLASS) - this.arena.set_start_offset(node, start) - this.arena.set_length(node, this.lexer.token_end - start) - this.arena.set_start_line(node, this.lexer.line) - this.arena.set_start_column(node, this.lexer.column) + let node = this.create_node( + is_pseudo_element ? NODE_SELECTOR_PSEUDO_ELEMENT : NODE_SELECTOR_PSEUDO_CLASS, + start, + this.lexer.token_end, + ) + // Content is the pseudo name (without colons) this.arena.set_content_start(node, this.lexer.token_start) this.arena.set_content_length(node, this.lexer.token_end - this.lexer.token_start) @@ -685,12 +666,7 @@ export class SelectorParser { } } - let node = this.arena.create_node() - this.arena.set_type(node, is_pseudo_element ? NODE_SELECTOR_PSEUDO_ELEMENT : NODE_SELECTOR_PSEUDO_CLASS) - this.arena.set_start_offset(node, start) - this.arena.set_length(node, end - start) - this.arena.set_start_line(node, this.lexer.line) - this.arena.set_start_column(node, this.lexer.column) + let node = this.create_node(is_pseudo_element ? NODE_SELECTOR_PSEUDO_ELEMENT : NODE_SELECTOR_PSEUDO_CLASS, start, end) // Content is the function name (without colons and parentheses) this.arena.set_content_start(node, func_name_start) this.arena.set_content_length(node, func_name_end - func_name_start) @@ -790,12 +766,7 @@ export class SelectorParser { // Accept both strings and identifiers if (token_type === TOKEN_STRING || token_type === TOKEN_IDENT) { // Create language identifier node - let lang_node = this.arena.create_node() - this.arena.set_type(lang_node, NODE_SELECTOR_LANG) - this.arena.set_start_offset(lang_node, token_start) - this.arena.set_length(lang_node, token_end - token_start) - this.arena.set_start_line(lang_node, this.lexer.line) - this.arena.set_start_column(lang_node, this.lexer.column) + let lang_node = this.create_node(NODE_SELECTOR_LANG, token_start, token_end) // Link as child of :lang() pseudo-class if (first_child === null) { @@ -897,59 +868,9 @@ export class SelectorParser { return -1 } - // Create simple selector nodes - private create_type_selector(start: number, end: number): number { - let node = this.arena.create_node() - this.arena.set_type(node, NODE_SELECTOR_TYPE) - this.arena.set_start_offset(node, start) - this.arena.set_length(node, end - start) - this.arena.set_start_line(node, this.lexer.line) - this.arena.set_start_column(node, this.lexer.column) - this.arena.set_content_start(node, start) - this.arena.set_content_length(node, end - start) - return node - } - - private create_id_selector(start: number, end: number): number { - let node = this.arena.create_node() - this.arena.set_type(node, NODE_SELECTOR_ID) - this.arena.set_start_offset(node, start) - this.arena.set_length(node, end - start) - this.arena.set_start_line(node, this.lexer.line) - this.arena.set_start_column(node, this.lexer.column) - // Content is the ID name (without the #) - this.arena.set_content_start(node, start + 1) - this.arena.set_content_length(node, end - start - 1) - return node - } - - private create_universal_selector(start: number, end: number): number { - let node = this.arena.create_node() - this.arena.set_type(node, NODE_SELECTOR_UNIVERSAL) - this.arena.set_start_offset(node, start) - this.arena.set_length(node, end - start) - this.arena.set_start_line(node, this.lexer.line) - this.arena.set_start_column(node, this.lexer.column) - this.arena.set_content_start(node, start) - this.arena.set_content_length(node, end - start) - return node - } - - private create_nesting_selector(start: number, end: number): number { - let node = this.arena.create_node() - this.arena.set_type(node, NODE_SELECTOR_NESTING) - this.arena.set_start_offset(node, start) - this.arena.set_length(node, end - start) - this.arena.set_start_line(node, this.lexer.line) - this.arena.set_start_column(node, this.lexer.column) - this.arena.set_content_start(node, start) - this.arena.set_content_length(node, end - start) - return node - } - - private create_combinator(start: number, end: number): number { + private create_node(type: number, start: number, end: number): number { let node = this.arena.create_node() - this.arena.set_type(node, NODE_SELECTOR_COMBINATOR) + this.arena.set_type(node, type) this.arena.set_start_offset(node, start) this.arena.set_length(node, end - start) this.arena.set_start_line(node, this.lexer.line)