Skip to content

Commit 962cad6

Browse files
authored
Merge pull request #129 from Ragnoroct/add-specificity-to-hover
Show specificity in a css selector hover.
2 parents 6ee07c9 + 488d76b commit 962cad6

File tree

3 files changed

+90
-10
lines changed

3 files changed

+90
-10
lines changed

src/services/cssHover.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class CSSHover {
1313

1414
constructor() {
1515
}
16-
16+
1717
public doHover(document: TextDocument, position: Position, stylesheet: nodes.Stylesheet): Hover {
1818

1919
function getRange(node: nodes.Node) {

src/services/selectorPrinting.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,14 +310,60 @@ function unescape(content: string) {
310310
return content;
311311
}
312312

313+
function selectorToSpecifityMarkedString(node: nodes.Node): MarkedString {
314+
//https://www.w3.org/TR/selectors-3/#specificity
315+
function calculateScore(node: nodes.Node) {
316+
node.getChildren().forEach(element => {
317+
switch(element.type) {
318+
case nodes.NodeType.IdentifierSelector:
319+
specificity[0] += 1; //a
320+
break;
321+
case nodes.NodeType.ClassSelector:
322+
case nodes.NodeType.AttributeSelector:
323+
specificity[1] += 1; //b
324+
break;
325+
case nodes.NodeType.ElementNameSelector:
326+
//ignore universal selector
327+
if (element.getText() === "*") {
328+
break;
329+
}
330+
specificity[2] += 1; //c
331+
break;
332+
case nodes.NodeType.PseudoSelector:
333+
if (element.getText().match(/^::/)) {
334+
specificity[2] += 1; //c (pseudo element)
335+
} else {
336+
//ignore psuedo class NOT
337+
if (element.getText().match(/^:not/i)) {
338+
break;
339+
}
340+
specificity[1] += 1; //b (pseudo class)
341+
}
342+
break;
343+
}
344+
if (element.getChildren().length > 0) {
345+
calculateScore(element);
346+
}
347+
});
348+
}
349+
350+
let specificity = [0, 0, 0]; //a,b,c
351+
calculateScore(node);
352+
return { language: 'text', value: "Specificity: " + specificity.join(",") };
353+
}
354+
313355
export function selectorToMarkedString(node: nodes.Selector): MarkedString[] {
314356
let root = selectorToElement(node);
315-
return new MarkedStringPrinter('"').print(root);
357+
let markedStrings = new MarkedStringPrinter('"').print(root);
358+
markedStrings.push(selectorToSpecifityMarkedString(node));
359+
return markedStrings;
316360
}
317361

318362
export function simpleSelectorToMarkedString(node: nodes.SimpleSelector): MarkedString[] {
319363
let element = toElement(node);
320-
return new MarkedStringPrinter('"').print(element);
364+
let markedStrings = new MarkedStringPrinter('"').print(element);
365+
markedStrings.push(selectorToSpecifityMarkedString(node));
366+
return markedStrings;
321367
}
322368

323369
class SelectorElementBuilder {

src/test/css/selectorPrinting.test.ts

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,22 +121,56 @@ suite('CSS - MarkedStringPrinter selectors', () => {
121121

122122
test('descendant selector', function () {
123123
let p = new Parser();
124-
parseSelectorToMarkedString(p, 'e1 e2 { }', 'e1', [{ language: 'html', value: '<e1>\n …\n <e2>' }]);
125-
parseSelectorToMarkedString(p, 'e1 .div { }', 'e1', [{ language: 'html', value: '<e1>\n …\n <element class="div">' }]);
124+
parseSelectorToMarkedString(p, 'e1 e2 { }', 'e1', [{ language: 'html', value: '<e1>\n …\n <e2>' }, { language: 'text', value: 'Specificity: 0,0,2'}]);
125+
parseSelectorToMarkedString(p, 'e1 .div { }', 'e1', [{ language: 'html', value: '<e1>\n …\n <element class="div">' }, { language: 'text', value: 'Specificity: 0,1,1'}]);
126126
});
127127
test('child selector', function () {
128128
let p = new Parser();
129-
parseSelectorToMarkedString(p, 'e1 > e2 { }', 'e2', [{ language: 'html', value: '<e1>\n <e2>' }]);
129+
parseSelectorToMarkedString(p, 'e1 > e2 { }', 'e2', [{ language: 'html', value: '<e1>\n <e2>' }, { language: 'text', value: 'Specificity: 0,0,2'}]);
130130
});
131131
test('group selector', function () {
132132
let p = new Parser();
133-
parseSelectorToMarkedString(p, 'e1, e2 { }', 'e1', [{ language: 'html', value: '<e1>' }]);
134-
parseSelectorToMarkedString(p, 'e1, e2 { }', 'e2', [{ language: 'html', value: '<e2>' }]);
133+
parseSelectorToMarkedString(p, 'e1, e2 { }', 'e1', [{ language: 'html', value: '<e1>' }, { language: 'text', value: 'Specificity: 0,0,1'}]);
134+
parseSelectorToMarkedString(p, 'e1, e2 { }', 'e2', [{ language: 'html', value: '<e2>' }, { language: 'text', value: 'Specificity: 0,0,1'}]);
135135
});
136136
test('sibling selector', function () {
137137
let p = new Parser();
138-
parseSelectorToMarkedString(p, 'e1 + e2 { }', 'e2', [{ language: 'html', value: '<e1>\n<e2>' }]);
139-
parseSelectorToMarkedString(p, 'e1 ~ e2 { }', 'e2', [{ language: 'html', value: '<e1>\n<e2>\n⋮\n<e2>' }]);
138+
parseSelectorToMarkedString(p, 'e1 + e2 { }', 'e2', [{ language: 'html', value: '<e1>\n<e2>' }, { language: 'text', value: 'Specificity: 0,0,2'}]);
139+
parseSelectorToMarkedString(p, 'e1 ~ e2 { }', 'e2', [{ language: 'html', value: '<e1>\n<e2>\n⋮\n<e2>' }, { language: 'text', value: 'Specificity: 0,0,2'}]);
140+
});
141+
});
142+
143+
suite('CSS - MarkedStringPrinter selectors specificities', () => {
144+
let p = new Parser();
145+
test('attribute selector', function() {
146+
parseSelectorToMarkedString(p, 'h1 + *[rel=up]', 'h1', [{ language: 'html', value: '<h1>\n<element rel="up">' }, { language: 'text', value: 'Specificity: 0,1,1' }]);
147+
});
148+
149+
test('class selector', function() {
150+
parseSelectorToMarkedString(p, 'ul ol li.red', 'ul', [{ language: 'html', value: '<ul>\n …\n <ol>\n …\n <li class="red">' }, { language: 'text', value: 'Specificity: 0,1,3' }]);
151+
parseSelectorToMarkedString(p, 'li.red.level', 'li', [{ language: 'html', value: '<li class="red level">' }, { language: 'text', value: 'Specificity: 0,2,1' }]);
152+
});
153+
154+
test('pseudo class selector', function() {
155+
parseSelectorToMarkedString(p, 'p:focus', 'p', [{ language: 'html', value: '<p :focus>' }, { language: 'text', value: 'Specificity: 0,1,1' }]);
156+
});
157+
158+
test('element selector', function() {
159+
parseSelectorToMarkedString(p, 'li', 'li', [{ language: 'html', value: '<li>' }, { language: 'text', value: 'Specificity: 0,0,1' }]);
160+
parseSelectorToMarkedString(p, 'ul li', 'ul', [{ language: 'html', value: '<ul>\n …\n <li>' }, { language: 'text', value: 'Specificity: 0,0,2' }]);
161+
parseSelectorToMarkedString(p, 'ul ol+li', 'ul', [{ language: 'html', value: '<ul>\n …\n <ol>\n <li>' }, { language: 'text', value: 'Specificity: 0,0,3' }]);
140162
});
141163

164+
test('pseudo element selector', function() {
165+
parseSelectorToMarkedString(p, 'p::after', 'p', [{ language: 'html', value: '<p ::after>' }, { language: 'text', value: 'Specificity: 0,0,2' }]);
166+
});
167+
168+
test('identifier selector', function() {
169+
parseSelectorToMarkedString(p, '#x34y', '#x34y', [{ language: 'html', value: '<element id="x34y">' }, { language: 'text', value: 'Specificity: 1,0,0' }]);
170+
});
171+
172+
test('ignore universal and not selector', function() {
173+
parseSelectorToMarkedString(p, '*', '*', [{ language: 'html', value: '<element>' }, { language: 'text', value: 'Specificity: 0,0,0' }]);
174+
parseSelectorToMarkedString(p, '#s12:not(foo)', '#s12', [{ language: 'html', value: '<element id="s12" :not>' }, { language: 'text', value: 'Specificity: 1,0,1' }]);
175+
});
142176
});

0 commit comments

Comments
 (0)