[dart:html] Script to generate the prototype JS interop CSS properties

Generates a `tools/dom/scripts/prototype_css_properties.dart` file
with a JS interop extension with the intersection of all the dart:html
CSS property getters and setters from CssStyleDeclaration and
CssStyleDeclarationBase.

Change-Id: Ic616cdd5111eb169206a31c40e62356a7fb32150
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/254561
Commit-Queue: Riley Porter <rileyporter@google.com>
Reviewed-by: Srujan Gaddam <srujzs@google.com>
diff --git a/tools/dom/scripts/prototype_css_generator.py b/tools/dom/scripts/prototype_css_generator.py
new file mode 100644
index 0000000..7cf54af
--- /dev/null
+++ b/tools/dom/scripts/prototype_css_generator.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2022, 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.
+"""Generates CssStyleDeclaration extension with all property getters and setters
+from css property definitions defined in WebKit."""
+
+import tempfile, os, re
+
+COMMENT_LINE_PREFIX = '   * '
+SOURCE_PATH = 'CSSPropertyNames.in'
+OUTPUT_FILE = 'prototype_css_properties.dart'
+
+# These are the properties that are supported on all Dart project supported
+# browsers as camelCased names on the CssStyleDeclaration.
+# Note that we do not use the MDN for compatibility info here.
+BROWSER_PATHS = [
+    'cssProperties.CSS21.txt',  # Remove when we have samples from all browsers.
+    'cssProperties.ie9.txt',
+    'cssProperties.ie10.txt',
+    'cssProperties.ie11.txt',
+    'cssProperties.ff36.txt',
+    'cssProperties.chrome40.txt',
+    'cssProperties.safari-7.1.3.txt',
+    'cssProperties.mobileSafari-8.2.txt',
+    'cssProperties.iPad4Air.onGoogleSites.txt',
+]
+
+# Supported annotations for any specific CSS properties.
+annotated = {
+    'transition':
+    '''
+  @SupportedBrowser(SupportedBrowser.CHROME)
+  @SupportedBrowser(SupportedBrowser.FIREFOX)
+  @SupportedBrowser(SupportedBrowser.IE, '10')
+  @SupportedBrowser(SupportedBrowser.SAFARI)'''
+}
+
+
+class Error:
+
+    def __init__(self, message):
+        self.message = message
+
+    def __repr__(self):
+        return self.message
+
+
+def camelCaseName(name):
+    """Convert a CSS property name to a lowerCamelCase name."""
+    name = name.replace('-webkit-', '')
+    words = []
+    for word in name.split('-'):
+        if words:
+            words.append(word.title())
+        else:
+            words.append(word)
+    return ''.join(words)
+
+
+def dashifyName(camelName):
+
+    def fix(match):
+        return '-' + match.group(0).lower()
+
+    return re.sub(r'[A-Z]', fix, camelName)
+
+
+def isCommentLine(line):
+    return line.strip() == '' or line.startswith('#') or line.startswith('//')
+
+
+def readCssProperties(filename):
+    data = open(filename).readlines()
+    data = sorted([d.strip() for d in set(data) if not isCommentLine(d)])
+    return data
+
+
+def main():
+    data = open(SOURCE_PATH).readlines()
+    data = [d.strip() for d in data if not isCommentLine(d) and not '=' in d]
+
+    browser_props = [set(readCssProperties(file)) for file in BROWSER_PATHS]
+    universal_properties = set.intersection(*browser_props)
+    universal_properties = universal_properties.difference(['cssText'])
+    universal_properties = universal_properties.intersection(
+        list(map(camelCaseName, data)))
+
+    output_file = open(OUTPUT_FILE, 'w')
+    output_file.write("""
+/// Exposing all the extra CSS property getters and setters.
+part of 'dart:html_extensions';
+
+extension CssStyleDeclarationPropertiesExtension on html.CssStyleDeclaration {
+
+  // ##### Universal property getters and setters #####
+  """)
+
+    for camelName in sorted(universal_properties):
+        property = dashifyName(camelName)
+        output_file.write("""
+  /** Gets the value of "%s" */
+  String get %s => this._%s;
+
+  /** Sets the value of "%s" */
+  set %s(String? value) {
+    _%s = value == null ? '' : value;
+  }
+
+  @JS('%s')
+  external String get _%s;
+
+  @JS('%s')
+  external set _%s(String value);
+    """ % (property, camelName, camelName, property, camelName, camelName,
+           camelName, camelName, camelName, camelName))
+
+    output_file.write("""
+
+  // ##### Non-universal property getters and setters #####
+
+""")
+
+    property_lines = []
+
+    seen = set()
+    for prop in sorted(data, key=camelCaseName):
+        camel_case_name = camelCaseName(prop)
+        upper_camel_case_name = camel_case_name[0].upper() + camel_case_name[1:]
+        css_name = prop.replace('-webkit-', '')
+        base_css_name = prop.replace('-webkit-', '')
+
+        if base_css_name in seen or base_css_name.startswith(
+                '-internal') or camel_case_name in universal_properties:
+            continue
+        seen.add(base_css_name)
+
+        comment = '  /** %s the value of "' + base_css_name + '" */'
+        property_lines.append('\n')
+        property_lines.append(comment % 'Gets')
+        if base_css_name in annotated:
+            property_lines.append(annotated[base_css_name])
+        property_lines.append("""
+  String get %s =>
+    getPropertyValue('%s');
+
+""" % (camel_case_name, css_name))
+
+        property_lines.append(comment % 'Sets')
+        if base_css_name in annotated:
+            property_lines.append(annotated[base_css_name])
+        property_lines.append("""
+  set %s(String value) {
+    setProperty('%s', value, '');
+  }
+""" % (camel_case_name, css_name))
+
+    output_file.write(''.join(property_lines))
+    output_file.write('}\n')
+    output_file.close()
+
+
+if __name__ == '__main__':
+    main()