Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional compat and add removed keywords #36

Merged
merged 9 commits into from
Apr 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: node_js

node_js:
- 'stable'
151 changes: 117 additions & 34 deletions index.d.ts

Large diffs are not rendered by default.

143 changes: 109 additions & 34 deletions index.js.flow

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"chokidar": "^2.0.0",
"flow-bin": "^0.69.0",
"jest": "^22.2.1",
"mdn-browser-compat-data": "0.0.30",
"mdn-data": "1.1.1",
"mdn-browser-compat-data": "git+https://github.com/mdn/browser-compat-data.git#3ab7b502352239d2131571d7463e098d2317532d",
"mdn-data": "git+https://github.com/mdn/data.git#af0369d9445c7e6f44f818d2034ccac16f328da9",
"prettier": "^1.10.2",
"ts-node": "^5.0.1",
"tslint": "^5.9.1",
Expand All @@ -32,7 +32,7 @@
"test": "jest --no-cache",
"typecheck": "tsc typecheck.ts --noEmit --pretty & flow check typecheck.js",
"prepublish": "tsc && npm run test && npm run build && npm run typecheck",
"rebase-build": "git rebase -s recursive -X theirs --exec \"yarn build && git commit -a --amend --no-verify --no-edit --allow-empty\""
"rebase-build": "git rebase -s recursive -X theirs --exec \"yarn --ignore-scripts && yarn build && git commit -a --amend --no-verify --no-edit --allow-empty\""
},
"files": [
"index.d.ts",
Expand Down
47 changes: 42 additions & 5 deletions src/at-rules.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,65 @@
import * as atRules from 'mdn-data/css/at-rules.json';
import { compatNames, compatSyntax, getAtRuleData, getCompat, isAddedBySome } from './compat';
import { resolveDataTypes } from './data-types';
import parse from './parser';
import typing, { IStringLiteral, ResolvedType, Type } from './typer';

interface IDescriptor {
name: string;
types: ResolvedType[];
}

export interface IAtRuleDescriptors {
[descriptor: string]: ResolvedType[];
[descriptor: string]: IDescriptor;
}

export const atRuleList: IStringLiteral[] = [];
export const atRuleDescriptors: { [name: string]: IAtRuleDescriptors } = {};

for (const name in atRules) {
const atRule = atRules[name];
for (const atName in atRules) {
const atRule = atRules[atName];

// Without the `@`
const name = atName.slice(1);

atRuleList.push({
type: Type.StringLiteral,
literal: name,
literal: atName,
});

if ('descriptors' in atRule) {
const descriptors: IAtRuleDescriptors = {};
const compatibilityData = getAtRuleData(name);

for (const descriptor in atRule.descriptors) {
descriptors[descriptor] = resolveDataTypes(typing(parse(atRule.descriptors[descriptor].syntax)));
let entities = parse(atRule.descriptors[descriptor].syntax);
let properties = [descriptor];

if (compatibilityData && descriptor in compatibilityData) {
const compat = getCompat(compatibilityData[descriptor]);

if (!isAddedBySome(compat)) {
// The property needs to be added by some browsers
continue;
}

entities = compatSyntax(compatibilityData, entities);
properties = properties.concat(
// We mix current and obsolete for now
compatNames(compat, descriptor)
.concat(compatNames(compat, descriptor, true))
.filter(property => !(property in atRule.descriptors)),
);
}

const types = resolveDataTypes(typing(entities));

for (const property of properties) {
descriptors[property] = {
name: descriptor,
types,
};
}
}

atRuleDescriptors[name] = descriptors;
Expand Down
202 changes: 145 additions & 57 deletions src/compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,107 @@ import { Combinator, combinatorData, Component, componentData, componentGroupDat

const importsCache: { [cssPath: string]: MDN.PropertiesCompat | null } = {};

function getData(cssPath: string): MDN.PropertiesCompat | null {
if (cssPath in importsCache) {
return importsCache[cssPath];
}
export function getCompat(data: { __compat: MDN.Compat }): MDN.Compat {
return data.__compat;
}

try {
return (importsCache[cssPath] = require(`mdn-browser-compat-data/css/${cssPath}.json`));
} catch {
return (importsCache[cssPath] = null);
export function getSupport(support: MDN.Support | MDN.Support[]): MDN.Support[] {
return Array.isArray(support) ? support : [support];
}

export function getAtRuleData(name: string): MDN.CompatData | null {
const data = getData('at-rules/' + name);

if (data) {
return data.css['at-rules'][name];
}

return null;
}

export function compatPropertyNames(name: string, onlyObsolete = false): string[] {
export function getPropertyData(name: string): MDN.CompatData | null {
const data = getData('properties/' + name);

const properties = [];
if (data) {
return data.css.properties[name];
}

return null;
}

export function getSelectorsData(name: string): MDN.CompatData | null {
const data = getData('selectors/' + name);

if (data) {
const compat = data.css.properties[name].__compat;
return data.css.selectors[name];
}

let browser: MDN.Browsers;
for (browser in compat.support) {
const support = compat.support[browser];
return null;
}

for (const version of Array.isArray(support) ? support : [support]) {
// Assume that the version has the property implemented if `null`
const hasBeenAdded = !!version.version_added || version.version_added === null;
const isObsolete = isDeprecated(name) || !!version.version_removed;
export function getTypesData(name: string): MDN.CompatData | null {
const data = getData('types/' + name);

if (hasBeenAdded && isObsolete === onlyObsolete) {
const compatName = version.prefix ? version.prefix + name : version.alternative_name;
if (data) {
return data.css.types[name];
}

if (compatName) {
properties.push(compatName);
}
}
}
}
return null;
}

function getData(cssPath: string): any {
if (cssPath in importsCache) {
return importsCache[cssPath];
}

return properties;
try {
return (importsCache[cssPath] = require(`mdn-browser-compat-data/css/${cssPath}.json`));
} catch {
return (importsCache[cssPath] = null);
}
}

export function isDeprecated(name: string) {
const data = getData('properties/' + name);
export function compatNames(compat: MDN.Compat, name: string, onlyRemoved = false): string[] {
const properties: string[] = [];

if (data) {
const status = data.css.properties[name].__compat.status;
let browser: MDN.Browsers;
for (browser in compat.support) {
const support = compat.support[browser];

// Assume not deprecated if is status i missing
return !!status && status.deprecated;
for (const version of getSupport(support)) {
// Assume that the version has the property implemented if `null`
const isAdded = !!version.version_added || version.version_added === null;
const isRemoved = !!version.version_removed;

if (isAdded && isRemoved === onlyRemoved) {
if (version.prefix) {
properties.push(version.prefix + name);
}
if (version.alternative_name) {
properties.push(version.alternative_name);
}
}
}
}

return null;
return properties;
}

export function compatPropertySyntax(name: string, entities: EntityType[]): EntityType[] {
export function compatSyntax(data: MDN.CompatData, entities: EntityType[]): EntityType[] {
const compatEntities: EntityType[] = [];

for (const entity of entities) {
if (entity.entity === Entity.Component) {
switch (entity.component) {
case Component.Keyword: {
const alternatives = alternativeKeywords(name, entity.value);
if (entity.value in data && !isAddedBySome(getCompat(data[entity.value]))) {
// The keyword needs to be added by some browsers so we remove previous
// combinator and skip this keyword
compatEntities.pop();
continue;
}

const alternatives = alternativeKeywords(data, entity.value);

if (alternatives.length > 0) {
const alternativeEntities: EntityType[] = [entity];
Expand All @@ -80,7 +117,7 @@ export function compatPropertySyntax(name: string, entities: EntityType[]): Enti
break;
}
case Component.Group: {
compatEntities.push(componentGroupData(compatPropertySyntax(name, entity.entities), entity.multiplier));
compatEntities.push(componentGroupData(compatSyntax(data, entity.entities), entity.multiplier));
continue;
}
}
Expand All @@ -92,37 +129,88 @@ export function compatPropertySyntax(name: string, entities: EntityType[]): Enti
return compatEntities;
}

function alternativeKeywords(name: string, value: string): string[] {
const data = getData('properties/' + name);
function alternativeKeywords(data: MDN.CompatData, value: string): string[] {
const alternatives: string[] = [];

if (data) {
const property = data.css.properties[name];
if (value in data) {
const compat = getCompat(data[value]);

let browser: MDN.Browsers;
for (browser in compat.support) {
const support = compat.support[browser];

for (const version of Array.isArray(support) ? support : [support]) {
const isCurrent =
// Assume that the version has the value implemented if `null`
!!version.version_added || version.version_added === null;

if (isCurrent) {
if (version.prefix && !alternatives.includes(version.prefix + value)) {
alternatives.push(version.prefix + value);
}
if (version.alternative_name && !alternatives.includes(version.alternative_name)) {
alternatives.push(version.alternative_name);
}
}
}
}
}

return alternatives;
}

if (value in property) {
const combat = property[value].__compat;
export function isDeprecated(compat: MDN.Compat) {
// Assume not deprecated if is status i missing
return !!compat.status && compat.status.deprecated;
}

let browser: MDN.Browsers;
for (browser in combat.support) {
const support = combat.support[browser];
export function isAddedBySome(compat: MDN.Compat): boolean {
let browser: MDN.Browsers;
for (browser in compat.support) {
const support = compat.support[browser];

for (const version of Array.isArray(support) ? support : [support]) {
const isCurrent =
// Assume that the version has the value implemented if `null`
(!!version.version_added || version.version_added === null) &&
// Assume that the version hasn't removed value if `null`
!version.version_removed;
for (const version of getSupport(support)) {
// Assume that the version has the property implemented if `null`
if (!!version.version_added || version.version_added === null) {
return true;
}
}
}

if (isCurrent) {
const compatName = version.prefix ? version.prefix + value : version.alternative_name;
return false;
}

if (compatName) {
alternatives.push(compatName);
}
export function alternativeSelectors(selector: string): string[] {
const alternatives: string[] = [];

// Pseudo without ':'
const colons = ':'.repeat(selector.lastIndexOf(':') + 1);
const name = selector.slice(colons.length);
const compatibilityData = getSelectorsData(name);

if (compatibilityData) {
const compat = getCompat(compatibilityData);

let browser: MDN.Browsers;
for (browser in compat.support) {
const support = compat.support[browser];

for (const version of getSupport(support)) {
// Assume that the version has the property implemented if `null`
const isAdded = !!version.version_added || version.version_added === null;

if (isAdded) {
if (version.prefix) {
alternatives.push(colons + version.prefix + name);
}
if (version.alternative_name) {
alternatives.push(version.alternative_name);
}
}
}
}

return alternatives;
}

return alternatives;
Expand Down
Loading