f34eef5b91
Migrates syntax and semantics from python 2.7. Major changes include: - filters - sorting - print statements - higher-order functions - hashing and comparison and other misc changes. go.sh consistently gives the libraries in this and the previous commits with these changes. Change-Id: I66365739887158d8f321015d36e556447da1bcd3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/211542 Commit-Queue: Srujan Gaddam <srujzs@google.com> Reviewed-by: Riley Porter <rileyporter@google.com>
331 lines
14 KiB
Python
331 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
|
|
# for details. All rights reserved. Use of this source code is governed by a
|
|
# BSD-style license that can be found in the LICENSE file.
|
|
|
|
import json
|
|
import os.path
|
|
import re
|
|
import sys
|
|
|
|
_COMPAT_KEY = '__compat'
|
|
_EXPERIMENTAL_KEY = 'experimental'
|
|
_STATUS_KEY = 'status'
|
|
_SUPPORT_KEY = 'support'
|
|
_VERSION_ADDED_KEY = 'version_added'
|
|
|
|
|
|
def _get_browser_compat_data():
|
|
current_dir = os.path.dirname(__file__)
|
|
|
|
browser_compat_folder = os.path.abspath(
|
|
os.path.join(current_dir, '..', '..', '..', 'third_party', 'mdn',
|
|
'browser-compat-data', 'src'))
|
|
|
|
if not os.path.exists(browser_compat_folder):
|
|
raise RuntimeError('Browser compatibility data not found at %s' %
|
|
browser_compat_folder)
|
|
|
|
browser_compat_data = {}
|
|
|
|
INCLUDE_DIRS = [
|
|
'api',
|
|
'html',
|
|
'svg',
|
|
# TODO(srujzs): add more if needed
|
|
]
|
|
|
|
# Transform to absolute paths
|
|
INCLUDE_DIRS = [
|
|
os.path.join(browser_compat_folder, dir) for dir in INCLUDE_DIRS
|
|
]
|
|
|
|
def process_json_dict(json_dict):
|
|
# Returns a tuple of the interface name and the metadata corresponding
|
|
# to it.
|
|
if 'api' in json_dict:
|
|
# Get the interface name
|
|
api_dict = json_dict['api']
|
|
interface_name = next(iter(api_dict))
|
|
return (interface_name, api_dict[interface_name])
|
|
elif 'html' in json_dict:
|
|
html_dict = json_dict['html']
|
|
if 'elements' in html_dict:
|
|
elements_dict = html_dict['elements']
|
|
element_name = next(iter(elements_dict))
|
|
# Convert to WebCore name
|
|
interface = str('HTML' + element_name + 'Element')
|
|
return (interface, elements_dict[element_name])
|
|
elif 'svg' in json_dict:
|
|
svg_dict = json_dict['svg']
|
|
if 'elements' in svg_dict:
|
|
elements_dict = svg_dict['elements']
|
|
element_name = next(iter(elements_dict))
|
|
# Convert to WebCore name
|
|
interface = str('SVG' + element_name + 'Element')
|
|
return (interface, elements_dict[element_name])
|
|
return (None, None)
|
|
|
|
# Attempts to unify two compatibility infos by taking the union of both, and
|
|
# for conflicting information, taking the "stricter" of the two versions.
|
|
# Updates `a` in place to represent the union of `a` and `b`.
|
|
def _unify_compat(a, b):
|
|
|
|
def _has_compat_data(metadata):
|
|
return _COMPAT_KEY in metadata and _SUPPORT_KEY in metadata[_COMPAT_KEY]
|
|
|
|
# Unifies the support statements of both metadata and updates
|
|
# `support_a` in place. If either metadata do not contain simple support
|
|
# statements, defaults attribute to not supported.
|
|
def _unify_support(support_a, support_b):
|
|
for browser in support_a.keys():
|
|
if browser in support_b:
|
|
if _is_simple_support_statement(support_a[browser]) and _is_simple_support_statement(support_b[browser]):
|
|
support_a[browser][_VERSION_ADDED_KEY] = _unify_versions(
|
|
support_a[browser][_VERSION_ADDED_KEY],
|
|
support_b[browser][_VERSION_ADDED_KEY])
|
|
else:
|
|
# Only support simple statements for now.
|
|
support_a[browser] = {_VERSION_ADDED_KEY: None}
|
|
for browser in support_b.keys():
|
|
if not browser in support_a:
|
|
support_a[browser] = support_b[browser]
|
|
|
|
if not _has_compat_data(b):
|
|
return
|
|
if not _has_compat_data(a):
|
|
a[_COMPAT_KEY] = b[_COMPAT_KEY]
|
|
return
|
|
|
|
support_a = a[_COMPAT_KEY][_SUPPORT_KEY]
|
|
support_b = b[_COMPAT_KEY][_SUPPORT_KEY]
|
|
|
|
_unify_support(support_a, support_b)
|
|
|
|
# Unifies any status info in the two metadata. Modifies `a` in place to
|
|
# represent the union of both `a` and `b`.
|
|
def _unify_status(a, b):
|
|
|
|
def _has_status(metadata):
|
|
return _COMPAT_KEY in metadata and _STATUS_KEY in metadata[_COMPAT_KEY]
|
|
|
|
# Modifies `status_a` in place to combine "experimental" tags.
|
|
def _unify_experimental(status_a, status_b):
|
|
# If either of the statuses report experimental, assume attribute is
|
|
# experimental.
|
|
status_a[_EXPERIMENTAL_KEY] = status_a.get(
|
|
_EXPERIMENTAL_KEY, False) or status_b.get(_EXPERIMENTAL_KEY, False)
|
|
|
|
if not _has_status(b):
|
|
return
|
|
if not _has_status(a):
|
|
a[_COMPAT_KEY] = b[_COMPAT_KEY]
|
|
return
|
|
|
|
status_a = a[_COMPAT_KEY][_STATUS_KEY]
|
|
status_b = b[_COMPAT_KEY][_STATUS_KEY]
|
|
|
|
_unify_experimental(status_a, status_b)
|
|
|
|
# If there exists multiple definitions of the same interface metadata e.g.
|
|
# elements, this attempts to unify the compatibilities for the interface as
|
|
# well as for each attribute.
|
|
def _unify_metadata(a, b):
|
|
# Unify the compatibility statement and status of the API or element.
|
|
_unify_compat(a, b)
|
|
_unify_status(a, b)
|
|
# Unify the compatibility statement and status of each attribute.
|
|
for attr in list(a.keys()):
|
|
if attr == _COMPAT_KEY:
|
|
continue
|
|
if attr in b:
|
|
_unify_compat(a[attr], b[attr])
|
|
_unify_status(a[attr], b[attr])
|
|
for attr in b.keys():
|
|
if not attr in a:
|
|
a[attr] = b[attr]
|
|
|
|
for (dir_path, dirs, files) in os.walk(browser_compat_folder):
|
|
|
|
def should_process_dir(dir_path):
|
|
if os.path.abspath(dir_path) == browser_compat_folder:
|
|
return True
|
|
for dir in INCLUDE_DIRS:
|
|
if dir_path.startswith(dir):
|
|
return True
|
|
return False
|
|
|
|
if should_process_dir(dir_path):
|
|
for name in files:
|
|
file_name = os.path.join(dir_path, name)
|
|
(interface_path, ext) = os.path.splitext(file_name)
|
|
if ext == '.json':
|
|
with open(file_name) as src:
|
|
json_dict = json.load(src)
|
|
interface, metadata = process_json_dict(json_dict)
|
|
if not interface is None:
|
|
# Note: interface and member names do not
|
|
# necessarily have the same capitalization as
|
|
# WebCore, so we keep them all lowercase for easier
|
|
# matching later.
|
|
interface = interface.lower()
|
|
metadata = {
|
|
member.lower(): info
|
|
for member, info in metadata.items()
|
|
}
|
|
|
|
if interface in browser_compat_data:
|
|
_unify_metadata(browser_compat_data[interface],
|
|
metadata)
|
|
else:
|
|
browser_compat_data[interface] = metadata
|
|
else:
|
|
dirs[:] = [] # Do not go underneath
|
|
|
|
return browser_compat_data
|
|
|
|
|
|
# Given two version values for a given browser, chooses the more strict version.
|
|
def _unify_versions(version_a, version_b):
|
|
# Given two valid version strings, compares parts of the version string
|
|
# iteratively.
|
|
def _greater_version(version_a, version_b):
|
|
version_a_split = list(map(int, version_a.split('.')))
|
|
version_b_split = list(map(int, version_b.split('.')))
|
|
for i in range(min(len(version_a_split), len(version_b_split))):
|
|
if version_a_split[i] > version_b_split[i]:
|
|
return version_a
|
|
elif version_a_split[i] < version_b_split[i]:
|
|
return version_b
|
|
return version_a if len(version_a_split) > len(
|
|
version_b_split) else version_b
|
|
|
|
# Validate that we can handle the given version.
|
|
def _validate_version(version):
|
|
if not version:
|
|
return False
|
|
if version is True:
|
|
return True
|
|
if isinstance(version, str):
|
|
pattern = re.compile('^([0-9]+\.)*[0-9]+$')
|
|
if not pattern.match(version):
|
|
# It's possible for version strings to look like '<35'. We don't
|
|
# attempt to parse the conditional logic, and just default to
|
|
# potentially incompatible.
|
|
return None
|
|
return version
|
|
else:
|
|
raise ValueError(
|
|
'Type of version_a was not handled correctly! type(version) = '
|
|
+ str(type(version)))
|
|
|
|
version_a = _validate_version(version_a)
|
|
version_b = _validate_version(version_b)
|
|
# If one version reports not supported, default to not supported.
|
|
if not version_a or not version_b:
|
|
return False
|
|
# If one version reports always supported, the other version can only be
|
|
# more strict.
|
|
if version_a is True:
|
|
return version_b
|
|
if version_b is True:
|
|
return version_a
|
|
|
|
return _greater_version(version_a, version_b)
|
|
|
|
|
|
# At this time, we only handle simple support statements due to the complexity
|
|
# and variability around support statements with multiple elements.
|
|
def _is_simple_support_statement(support_statement):
|
|
if isinstance(support_statement, list): # array_support_statement
|
|
# TODO(srujzs): Parse this list to determine compatibility. Will
|
|
# likely require parsing for 'version_removed' keys. Notes about
|
|
# which browser version enabled this attribute for which
|
|
# platform also complicates things. For now, we assume it's not
|
|
# compatible.
|
|
return False
|
|
if len(support_statement.keys()) > 1:
|
|
# If it's anything more complicated than 'version_added', like
|
|
# 'notes' that specify platform versions, we assume it's not
|
|
# compatible.
|
|
return False
|
|
return True
|
|
|
|
|
|
class MDNReader(object):
|
|
# Statically initialize and treat as constant.
|
|
_BROWSER_COMPAT_DATA = _get_browser_compat_data()
|
|
|
|
def __init__(self):
|
|
self._compat_overrides = {}
|
|
|
|
def _get_attr_compatibility(self, compat_data):
|
|
# Parse schema syntax of MDN data:
|
|
# https://github.com/mdn/browser-compat-data/blob/master/schemas/compat-data.schema.json
|
|
|
|
# For now, we will require support for browsers since the last IDL roll.
|
|
# TODO(srujzs): Determine if this is too conservative.
|
|
browser_version_map = {
|
|
'chrome': '63',
|
|
'firefox': '57',
|
|
'safari': '11',
|
|
# We still support the latest version of IE.
|
|
'ie': '11',
|
|
'opera': '50',
|
|
}
|
|
for browser in browser_version_map.keys():
|
|
support_data = compat_data[_SUPPORT_KEY]
|
|
if browser not in support_data:
|
|
return False
|
|
support_statement = support_data[browser]
|
|
if not _is_simple_support_statement(support_statement):
|
|
return False
|
|
version = support_statement[_VERSION_ADDED_KEY]
|
|
# Compare version strings, target should be the more strict version.
|
|
target = browser_version_map[browser]
|
|
if _unify_versions(version, target) != target:
|
|
return False
|
|
|
|
# If the attribute is experimental, we assume it's not compatible.
|
|
status_data = compat_data[_STATUS_KEY]
|
|
if _EXPERIMENTAL_KEY in status_data and status_data[_EXPERIMENTAL_KEY]:
|
|
return False
|
|
return True
|
|
|
|
def is_compatible(self, attribute):
|
|
# Since capitalization isn't consistent across MDN and WebCore, we
|
|
# compare lowercase equivalents for interface and attribute names.
|
|
interface = attribute.doc_js_interface_name.lower()
|
|
if interface in self._BROWSER_COMPAT_DATA and attribute.id and len(
|
|
attribute.id) > 0:
|
|
interface_dict = self._BROWSER_COMPAT_DATA[interface]
|
|
id_name = attribute.id.lower()
|
|
secure_context_key = 'isSecureContext'
|
|
if interface in self._compat_overrides and id_name in self._compat_overrides[
|
|
interface]:
|
|
return self._compat_overrides[interface][id_name]
|
|
elif secure_context_key in interface_dict:
|
|
# If the interface requires a secure context, all attributes are
|
|
# implicitly incompatible.
|
|
return False
|
|
elif id_name in interface_dict:
|
|
id_data = interface_dict[id_name]
|
|
return self._get_attr_compatibility(id_data[_COMPAT_KEY])
|
|
else:
|
|
# Might be an attribute that is defined in a parent interface.
|
|
# We defer until attribute emitting to determine if this is the
|
|
# case. Otherwise, return None.
|
|
pass
|
|
return None
|
|
|
|
def set_compatible(self, attribute, compatible):
|
|
# Override value in the MDN browser compatibility data.
|
|
if not compatible in [True, False, None]:
|
|
raise ValueError('Cannot set a non-boolean object for compatible')
|
|
interface = attribute.doc_js_interface_name.lower()
|
|
if not interface in self._compat_overrides:
|
|
self._compat_overrides[interface] = {}
|
|
if attribute.id and len(attribute.id) > 0:
|
|
id_name = attribute.id.lower()
|
|
self._compat_overrides[interface][id_name] = compatible
|