Skip to content

Commit

Permalink
Merge pull request #36 from frenic/additional-compat
Browse files Browse the repository at this point in the history
Additional compat and add removed keywords
  • Loading branch information
frenic authored Apr 13, 2018
2 parents de00002 + 441b2ce commit e1af19f
Show file tree
Hide file tree
Showing 12 changed files with 591 additions and 215 deletions.
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

0 comments on commit e1af19f

Please sign in to comment.