unicode-range
";
93 | return compat;
94 | }
95 |
96 | function handleTypeselector(node: TypeSelector): Identifier | void{
97 | let compat = bcd.css.selectors.type;
98 | if(node.name.includes("|")) {
99 | compat = compat.namespaces;
100 | compat.__compat!.mdn_url = "https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors#namespaces";
101 | }
102 | return compat;
103 | }
104 |
105 | function handleIdentifier(identifierNode: IdentifierNode, nodePath: CssNode[]): Identifier | void{
106 | return findByDeclaration(identifierNode, nodePath);
107 | }
108 |
109 | function handleDeclaration(node: Declaration): Identifier | void{
110 | return bcd.css.properties[node.property];
111 | }
112 |
113 | function getBCDdata(node: CssNode, nodePath: CssNode[]): Identifier | null{
114 | // Css Node Types: https://github.com/csstree/csstree/blob/master/docs/ast.md
115 | try{
116 | switch(node.type){
117 | case "Atrule":
118 | const atrule = handleAtRule(node);
119 | if(atrule) return atrule;
120 | throw new Error();
121 | case "AttributeSelector":
122 | const res = handleAttributeSelector(node);
123 | if(res) return res;
124 | throw new Error();
125 | case "ClassSelector":
126 | return bcd.css.selectors.class;
127 | case "Combinator":
128 | const comb = handleCombinaor(node);
129 | if(comb) return comb;
130 | throw new Error();
131 | case "Declaration":
132 | const dec = handleDeclaration(node);
133 | if(dec) return dec;
134 | throw new Error();
135 | case "Dimension":
136 | return bcd.css.types.dimension;
137 | case "Function":
138 | const func = handleFunctions(node, nodePath);
139 | if(func) return func;
140 | throw new Error();
141 | case "MediaFeature":
142 | const media = handleMediaFeatures(node);
143 | if(media) return media;
144 | throw new Error();
145 | case "IdSelector":
146 | return bcd.css.selectors.id;
147 | case "Identifier":
148 | const identifier = handleIdentifier(node, nodePath);
149 | if(identifier) return identifier;
150 | throw new Error();
151 | // @ts-ignore this type does exists in documentation
152 | case "NestingSelector":
153 | return bcd.css.selectors.nesting;
154 | case "Number":
155 | return bcd.css.types.number;
156 | case "Percentage":
157 | return bcd.css.types.percentage;
158 | case "PseudoClassSelector":
159 | return bcd.css.selectors[node.name];
160 | case "PseudoElementSelector":
161 | return bcd.css.selectors[node.name];
162 | case "Ratio":
163 | return bcd.css.types.ratio;
164 | case "SelectorList":
165 | return bcd.css.selectors.list;
166 | case "String":
167 | return bcd.css.types.string;
168 | case "TypeSelector":
169 | const typeSelector = handleTypeselector(node);
170 | if(typeSelector) return typeSelector;
171 | throw new Error();
172 | case "UnicodeRange":
173 | const unicode = handleUnicodeRange();
174 | if(unicode) return unicode;
175 | throw new Error();
176 | case "Url":
177 | return bcd.css.types.url;
178 | default:
179 | throw new Error();
180 | }
181 | }
182 | catch(e){
183 | console.log(`Type not found '${node.type}'`, node);
184 | return null;
185 | }
186 | }
187 |
188 | function findInCss(parsedCss: CssNode, word: string, offset: number): Identifier | null {
189 | const nodePath = findAll(parsedCss,(node, item, list) => {
190 | if(!node.loc) return false;
191 | return offset >= node.loc.start.offset && offset <= node.loc.end.offset;
192 | });
193 |
194 | const node = nodePath.at(-1);
195 | if(node){
196 | return getBCDdata(node, nodePath);
197 | }
198 | return null;
199 | }
200 |
201 | export default findInCss;
--------------------------------------------------------------------------------
/src/hoverProvider.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from "vscode";
2 | import path from "path";
3 | import CanICode from "./CanICode";
4 | import { aestricImage, deleteImage, testImage, warnImage } from "./Icons";
5 |
6 | export default function provideHover(
7 | document: vscode.TextDocument,
8 | position: vscode.Position,
9 | token: vscode.CancellationToken,
10 | context: vscode.ExtensionContext,
11 | fileType: string
12 | ) {
13 | const range = document.getWordRangeAtPosition(position);
14 | const word = document.getText(range);
15 | const offset = document.offsetAt(position);
16 | const theme = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark ? "dark" : "light";
17 | const userConfig = vscode.workspace.getConfiguration("css.compatibility");
18 |
19 | const canICode = CanICode._getInstance(document);
20 | canICode.setTheme(theme);
21 | canICode.setUserConfig(userConfig);
22 |
23 | const result = canICode.getCompatibilityData(word, offset, fileType);
24 |
25 | const hoverContent = new vscode.MarkdownString();
26 |
27 | let isAnyFlag = false;
28 | if(result.status?.deprecated === true){
29 | hoverContent.appendMarkdown(`${deleteImage} | Deprecated: This feature is no longer recommended. |
${testImage} | Experimental: This is an experimental technology. Check the Browser compatibility table carefully before using this in production. |
${warnImage} | Non-standard: This feature is non-standard and is not on a standards track. |
${aestricImage} | Documentation contains some notes of usage. |
${word}
`}`);
56 | }
57 | else if(result.description){
58 | hoverContent.appendMarkdown(`${result.description}
`);
59 | }
60 |
61 | hoverContent.supportHtml = true;
62 | hoverContent.isTrusted = true;
63 | hoverContent.baseUri = vscode.Uri.file(
64 | path.join(context.extensionPath, "images", path.sep)
65 | );
66 |
67 | return new vscode.Hover(hoverContent, range);
68 | }
--------------------------------------------------------------------------------
/src/scripts/extract-compats.js:
--------------------------------------------------------------------------------
1 | const bcd = require("@mdn/browser-compat-data");
2 | const fs = require("fs");
3 |
4 | // recursive function to extract all the compats by (keycheck, identifier) filters from given path
5 | function mohilMobil(path, keyCheck, identifier, level, mdn_url) {
6 | if(level === 0) return {};
7 | const object = path.split(".").reduce((o, k) => o ? o[k] : undefined, bcd);
8 | if(path.split(".").pop() === "__compat") {
9 | if (object[keyCheck] && object[keyCheck].includes(identifier)) {
10 | const value = path.split("."); // ["css", "properties", "background-color", "__compat"]
11 | value.pop(); // ["css", "properties", "background-color"]
12 | const key = value[value.length - 1]; // "background-color"
13 | return { [key]: {path: value.join("."), mdn_url} }; // { "background-color": "css.properties.background-color" }
14 | }
15 | return {}
16 | }
17 | let results = {};
18 | for (const key in object) {
19 | mdn_url = object[key].mdn_url ? object[key].mdn_url : (object["__compat"]?.mdn_url ? object["__compat"].mdn_url : mdn_url);
20 | results = { ...results, ...mohilMobil(`${path}.${key}`, keyCheck, identifier, level-1, mdn_url) };
21 | mdn_url = null;
22 | }
23 | return results;
24 | }
25 |
26 | const properties = mohilMobil("css.properties", "description", "()", 999, null);
27 | const atRules = mohilMobil("css.at-rules", "description", "()", 999, null);
28 | const selectors = mohilMobil("css.selectors", "description", "()", 999, null);
29 | const types = mohilMobil("css.types", "description", "()", 999, null);
30 | const functions = { ...properties, ...atRules, ...selectors, ...types };
31 |
32 | fs.writeFileSync("src/data/functions.json", JSON.stringify(functions, null, 2));
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BrowserType,
3 | CompatStatement,
4 | BrowserName,
5 | Browsers,
6 | SimpleSupportStatement,
7 | VersionValue,
8 |
9 | } from "@mdn/browser-compat-data/types";
10 |
11 | export type Theme = "dark" | "light";
12 | export type ClientType = Exclude