From 99a13e324322a6b74bde652fe6d300fb64e7428f Mon Sep 17 00:00:00 2001 From: Wilson Lin Date: Fri, 19 Jun 2020 17:16:23 +1000 Subject: [PATCH] Precompute generated files instead of building them every compile --- .gitignore | 1 + Cargo.lock | 59 - Cargo.toml | 8 +- build.rs | 309 --- gen/.gitignore | 2 +- gen/_common.ts | 37 + gen/attrs.json | 4431 ---------------------------------- gen/attrs.ts | 210 ++ gen/build/attrs.js | 215 -- gen/build/package.json | 8 - gen/data/attrs.json | 160 ++ gen/data/dfa.yaml | 77 + gen/{ => data}/entities.json | 0 gen/{ => data}/patterns.json | 0 gen/data/react.d.ts | 3150 ++++++++++++++++++++++++ gen/dfa.ts | 71 + gen/entities.ts | 28 + gen/package.json | 9 + gen/patterns.ts | 31 + gen/trie.ts | 171 ++ gen/tsconfig.json | 29 + 21 files changed, 3976 insertions(+), 5030 deletions(-) delete mode 100644 build.rs create mode 100644 gen/_common.ts delete mode 100644 gen/attrs.json create mode 100644 gen/attrs.ts delete mode 100644 gen/build/attrs.js delete mode 100644 gen/build/package.json create mode 100644 gen/data/attrs.json create mode 100644 gen/data/dfa.yaml rename gen/{ => data}/entities.json (100%) rename gen/{ => data}/patterns.json (100%) create mode 100644 gen/data/react.d.ts create mode 100644 gen/dfa.ts create mode 100644 gen/entities.ts create mode 100644 gen/package.json create mode 100644 gen/patterns.ts create mode 100644 gen/trie.ts create mode 100644 gen/tsconfig.json diff --git a/.gitignore b/.gitignore index ea8c4bf..3c8d3cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/src/gen/ diff --git a/Cargo.lock b/Cargo.lock index 40d609e..1118515 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,11 +23,6 @@ name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "cascade" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "cfg-if" version = "0.1.10" @@ -47,11 +42,6 @@ dependencies = [ "vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "fastrie" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "getrandom" version = "0.1.14" @@ -82,19 +72,10 @@ dependencies = [ name = "hyperbuild" version = "0.0.45" dependencies = [ - "cascade 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fastrie 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "phf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "itoa" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "lazy_static" version = "1.4.0" @@ -241,39 +222,6 @@ dependencies = [ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "ryu" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde" -version = "1.0.110" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_derive" -version = "1.0.110" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "serde_json" -version = "1.0.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "siphasher" version = "0.3.3" @@ -387,14 +335,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -"checksum cascade 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "31c9ddf4a1a9dbf82e130117f81b0c292fb5416000cbaba11eb92a65face2613" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" -"checksum fastrie 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "16a8e873087682100de15eaccd3f4671c44fe589bd8989a854c061c961884d16" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hermit-abi 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "61565ff7aaace3525556587bd2dc31d4a07071957be715e63ce7b1eccf51a8f4" -"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005" "checksum phf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" @@ -412,10 +357,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" "checksum rand_pcg 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -"checksum ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" -"checksum serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" -"checksum serde_derive 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)" = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" -"checksum serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)" = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" "checksum siphasher 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "863246aaf5ddd0d6928dfeb1a9ca65f505599e4e1b399935ef7e75107516b4ef" diff --git a/Cargo.toml b/Cargo.toml index e40b62d..610373a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,20 +10,14 @@ repository = "https://github.com/wilsonzlin/hyperbuild.git" version = "0.0.45" authors = ["Wilson Lin "] edition = "2018" -include = ["/gen/*.json", "/src/**/*", "/build.rs", "/Cargo.toml", "/LICENSE", "/README.md"] +include = ["/src/**/*", "/Cargo.toml", "/LICENSE", "/README.md"] [badges] maintenance = { status = "actively-developed" } [dependencies] -cascade = "0.1.4" -fastrie = "0.0.6" phf = { version = "0.8.0", features = ["macros"] } structopt = "0.3.5" -[build-dependencies] -serde = { version = "1.0.104", features = ["derive"] } -serde_json = "1.0.44" - [profile.release] panic = 'abort' diff --git a/build.rs b/build.rs deleted file mode 100644 index a42bad3..0000000 --- a/build.rs +++ /dev/null @@ -1,309 +0,0 @@ -use std::collections::HashMap; -use std::env; -use std::fs::File; -use std::io::Write; -use std::ops::{Index, IndexMut}; -use std::path::Path; - -use serde::{Deserialize, Serialize}; - -fn create_byte_string_literal(bytes: &[u8]) -> String { - format!("b\"{}\"", bytes - .iter() - .map(|&b| if b >= b' ' && b <= b'~' && b != b'\\' && b != b'"' { - (b as char).to_string() - } else { - format!("\\x{:02x}", b) - }) - .collect::()) -} - -fn read_json(name: &str) -> T where for<'de> T: Deserialize<'de> { - let patterns_path = Path::new("gen").join(format!("{}.json", name)); - let patterns_file = File::open(patterns_path).unwrap(); - serde_json::from_reader(patterns_file).unwrap() -} - -fn write_rs(name: &str, code: String) -> () { - let out_dir = env::var("OUT_DIR").unwrap(); - let dest_path = Path::new(&out_dir).join(format!("gen_{}.rs", name)); - let mut dest_file = File::create(&dest_path).unwrap(); - dest_file.write_all(code.as_bytes()).unwrap(); -} - -fn name_words(n: &str) -> Vec { - n.split(' ').map(|w| w.to_string()).collect::>() -} - -fn snake_case(n: &Vec) -> String { - n.iter().map(|w| w.to_uppercase()).collect::>().join("_") -} - -fn camel_case(n: &Vec) -> String { - n.iter().map(|w| format!( - "{}{}", - w.as_bytes()[0].to_ascii_uppercase() as char, - std::str::from_utf8(&w.as_bytes()[1..]).unwrap(), - )).collect::>().join("") -} - -pub struct TwoDimensionalArray { - data: Vec, - cols: usize, -} - -impl TwoDimensionalArray { - pub fn new(rows: usize, cols: usize) -> TwoDimensionalArray { - TwoDimensionalArray { - data: vec![0usize; rows * cols], - cols, - } - } - - pub fn prebuilt(data: Vec, cols: usize) -> TwoDimensionalArray { - TwoDimensionalArray { data, cols } - } -} - -type TwoDimensionalArrayIndex = (usize, usize); - -impl Index for TwoDimensionalArray { - type Output = usize; - - fn index(&self, (row, col): TwoDimensionalArrayIndex) -> &Self::Output { - &self.data[row * self.cols + col] - } -} - -impl IndexMut for TwoDimensionalArray { - fn index_mut(&mut self, (row, col): TwoDimensionalArrayIndex) -> &mut Self::Output { - &mut self.data[row * self.cols + col] - } -} - -fn build_pattern(pattern: String) -> String { - assert!(pattern.is_ascii()); - let seq = pattern.as_bytes(); - let dfa = &mut TwoDimensionalArray::new(256, seq.len()); - - dfa[(seq[0] as usize, 0)] = 1; - let mut x = 0; - let mut j = 1; - while j < seq.len() { - for c in 0..256 { - dfa[(c, j)] = dfa[(c, x)]; - }; - dfa[(seq[j] as usize, j)] = j + 1; - x = dfa[(seq[j] as usize, x)]; - j += 1; - }; - - format!( - "crate::pattern::SinglePattern::prebuilt(&[{}], {})", - dfa.data.iter().map(|v| v.to_string()).collect::>().join(", "), - seq.len(), - ) -} - -#[derive(Serialize, Deserialize)] -struct TagAttr { - boolean: bool, - redundant_if_empty: bool, - collapse_and_trim: bool, - default_value: Option, -} - -impl TagAttr { - fn code(&self) -> String { - format!(r" - AttributeMinification {{ - boolean: {boolean}, - redundant_if_empty: {redundant_if_empty}, - collapse_and_trim: {collapse_and_trim}, - default_value: {default_value}, - }} - ", - boolean = self.boolean, - redundant_if_empty = self.redundant_if_empty, - collapse_and_trim = self.collapse_and_trim, - default_value = match &self.default_value { - Some(val) => format!("Some({})", create_byte_string_literal(val.as_bytes())), - None => "None".to_string(), - }, - ) - } -} - -fn generate_attr_map() { - let attrs: HashMap>> = read_json("attrs"); - let mut code = String::new(); - for (attr_name, namespaces) in attrs.iter() { - let mut by_namespace_code = String::new(); - by_namespace_code.push_str(format!("static {}_ATTR: ByNamespace = ByNamespace {{\n", attr_name.to_uppercase()).as_str()); - for namespace in ["html".to_string(), "svg".to_string()].iter() { - by_namespace_code.push_str(format!("\t{}: ", namespace).as_str()); - match namespaces.get(namespace) { - None => by_namespace_code.push_str("None"), - Some(tags_map) => { - if let Some(global_attr) = tags_map.get("*") { - code.push_str(format!( - "static {}_{}_ATTR: &AttrMapEntry = &AttrMapEntry::AllNamespaceElements({});\n\n", - namespace.to_uppercase(), - attr_name.to_uppercase(), - global_attr.code(), - ).as_str()); - } else { - code.push_str(format!( - "static {}_{}_ATTR: &AttrMapEntry = &AttrMapEntry::SpecificNamespaceElements(phf::phf_map! {{\n{}\n}});\n\n", - namespace.to_uppercase(), - attr_name.to_uppercase(), - tags_map - .iter() - .map(|(tag_name, tag_attr)| format!( - "b\"{}\" => {}", - tag_name, - tag_attr.code(), - )) - .collect::>() - .join(",\n"), - ).as_str()); - }; - by_namespace_code.push_str(format!("Some({}_{}_ATTR)", namespace.to_uppercase(), attr_name.to_uppercase()).as_str()); - } - }; - by_namespace_code.push_str(",\n"); - }; - by_namespace_code.push_str("};\n\n"); - code.push_str(&by_namespace_code); - }; - code.push_str("pub static ATTRS: AttrMap = AttrMap::new(phf::phf_map! {\n"); - for attr_name in attrs.keys() { - code.push_str(format!("\tb\"{}\" => {}_ATTR,\n", attr_name, attr_name.to_uppercase()).as_str()); - }; - code.push_str("});\n\n"); - write_rs("attrs", code); -} - -#[derive(Serialize, Deserialize)] -struct Entity { - codepoints: Vec, - characters: String, -} - -pub struct TrieBuilderNode { - value: Option, - children: Vec>, -} - -struct TrieBuilderGenerationContext<'t, 'v, 'o> { - trie_name: &'t str, - value_type: &'v str, - next_id: usize, - out: &'o mut String, -} - -impl<'t, 'v, 'o> TrieBuilderGenerationContext<'t, 'v, 'o> { - pub fn id(&mut self) -> usize { - let next = self.next_id; - self.next_id += 1; - next - } -} - -impl TrieBuilderNode { - pub fn new() -> TrieBuilderNode { - let mut children = Vec::new(); - for _ in 0..256 { - children.push(None); - }; - TrieBuilderNode { value: None, children } - } - - pub fn add(&mut self, seq: &[u8], value: String) -> () { - let mut current = self; - for c in seq.iter() { - current = current.children[*c as usize].get_or_insert_with(|| TrieBuilderNode::new()); - }; - current.value.replace(value); - } - - fn _generated_node_var_name(&self, trie_name: &str, node_id: usize) -> String { - format!("{trie_name}_NODE_{node_id}", trie_name = trie_name, node_id = node_id) - } - - fn _generate(&self, ctx: &mut TrieBuilderGenerationContext) -> usize { - let children = self.children.iter().map(|c| match c { - None => "None".to_string(), - Some(c) => { - let child_id = c._generate(ctx); - format!("Some({})", self._generated_node_var_name(ctx.trie_name, child_id)) - } - }).collect::>().join(", "); - let id = ctx.id(); - let code = format!( - "static {var_name}: &'static crate::pattern::TrieNode<{value_type}> = &crate::pattern::TrieNode {{\n\tvalue: {value},\n\tchildren: [{children}],\n}};\n\n", - var_name = self._generated_node_var_name(ctx.trie_name, id), - value_type = ctx.value_type, - value = self.value.as_ref().map_or("None".to_string(), |v| format!("Some({})", v)), - children = children, - ); - ctx.out.push_str(code.as_str()); - id - } - - pub fn generate(&self, trie_name: &str, value_type: &str) -> String { - let mut out = String::new(); - let mut ctx = TrieBuilderGenerationContext { - trie_name, - value_type, - next_id: 0, - out: &mut out, - }; - let root_id = self._generate(&mut ctx); - // Make root node public and use proper name. - ctx.out.replace( - format!("static {}", self._generated_node_var_name(trie_name, root_id)).as_str(), - format!("pub static {}", trie_name).as_str() - ) - } -} - -fn generate_entities() { - // Read named entities map from JSON file. - let entities: HashMap = read_json("entities"); - - // Add entities to trie builder. - let mut trie_builder: TrieBuilderNode = TrieBuilderNode::new(); - for (rep, entity) in entities { - let val = if rep.as_bytes().len() < entity.characters.as_bytes().len() { - // Since we're minifying in place, we need to guarantee we'll never write something longer than source. - println!("Entity {} is shorter than decoded UTF-8 bytes...", rep); - // Include '&' in value. - create_byte_string_literal(rep.as_bytes()) - } else { - create_byte_string_literal(entity.characters.as_bytes()) - }; - trie_builder.add(&(rep.as_bytes())[1..], val); - }; - // Write trie code to output Rust file. - write_rs("entities", trie_builder.generate( - "ENTITY_REFERENCES", - "&'static [u8]", - )); -} - -fn generate_patterns() { - let patterns: HashMap = read_json("patterns"); - - for (name, pattern) in patterns { - let mut code = String::new(); - code.push_str(format!("static {}: &crate::pattern::SinglePattern = &{};", name, build_pattern(pattern)).as_str()); - write_rs(format!("pattern_{}", name).as_str(), code); - }; -} - -fn main() { - generate_attr_map(); - generate_entities(); - generate_patterns(); -} diff --git a/gen/.gitignore b/gen/.gitignore index 5dddf7a..0475271 100644 --- a/gen/.gitignore +++ b/gen/.gitignore @@ -1,2 +1,2 @@ node_modules/ -build/*.d.ts +/package-lock.json diff --git a/gen/_common.ts b/gen/_common.ts new file mode 100644 index 0000000..898e1d2 --- /dev/null +++ b/gen/_common.ts @@ -0,0 +1,37 @@ +import { join } from "path"; +import {mkdirSync, writeFileSync} from 'fs'; + +export const RUST_OUT_DIR = join(__dirname, '..', 'src', 'gen'); + +try { + mkdirSync(RUST_OUT_DIR); +} catch (err) { + if (err.code !== 'EEXIST') { + throw err; + } +} +writeFileSync(join(RUST_OUT_DIR, 'mod.rs'), ` +pub mod attrs; +pub mod dfa; +pub mod entities; +pub mod patterns; +`); + +export const DATA_DIR = join(__dirname, 'data'); + +export const leftPad = (str: string, n: number) => '0'.repeat(n - str.length) + str; + +export const prettyJson = (v: any) => JSON.stringify(v, null, 2); + +export const byteStringLiteral = (bytes: number[]): string => 'b"' + bytes.map(c => { + if (c > 255) throw new Error('Not a byte'); + // 0x20 == ' '. + // 0x7E == '~'. + // 0x5C == '\\'. + // 0x22 == '"'. + if (c >= 0x20 && c <= 0x7E && c != 0x5C && c != 0x22) { + return String.fromCharCode(c); + } else { + return `\\x${leftPad(c.toString(16), 2)}`; + } +}).join('') + '"'; diff --git a/gen/attrs.json b/gen/attrs.json deleted file mode 100644 index 2326b7e..0000000 --- a/gen/attrs.json +++ /dev/null @@ -1,4431 +0,0 @@ -{ - "abbr": { - "html": { - "td": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "th": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "about": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "accentheight": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "accept": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "acceptcharset": { - "html": { - "form": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "accesskey": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "accumulate": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "action": { - "html": { - "form": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "additive": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "align": { - "html": { - "td": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "th": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "alignmentbaseline": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "allow": { - "html": { - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "allowfullscreen": { - "html": { - "iframe": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "allowreorder": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "allowtransparency": { - "html": { - "iframe": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "alphabetic": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "alt": { - "html": { - "area": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "img": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "amplitude": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "arabicform": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "as": { - "html": { - "link": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "ascent": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "async": { - "html": { - "script": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "attributename": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "attributetype": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "autocapitalize": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "autocomplete": { - "html": { - "form": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "select": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "autocorrect": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "autofocus": { - "html": { - "button": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "input": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "keygen": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "select": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "textarea": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "autoplay": { - "html": { - "media": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "autoreverse": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "autosave": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "azimuth": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "basefrequency": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "baselineshift": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "baseprofile": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "bbox": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "begin": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "bias": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "by": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "calcmode": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "capheight": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "capture": { - "html": { - "input": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "cellpadding": { - "html": { - "table": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "cellspacing": { - "html": { - "table": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "challenge": { - "html": { - "keygen": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "charset": { - "html": { - "meta": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "script": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "checked": { - "html": { - "input": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "cite": { - "html": { - "blockquote": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "del": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "ins": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "quote": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "class": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": true - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "classid": { - "html": { - "object": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "clip": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "clippath": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "clippathunits": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "cliprule": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "color": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "colorinterpolation": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "colorinterpolationfilters": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "colorprofile": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "colorrendering": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "cols": { - "html": { - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "colspan": { - "html": { - "td": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "th": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "content": { - "html": { - "meta": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "contenteditable": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "contentscripttype": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "contentstyletype": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "contextmenu": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "controls": { - "html": { - "media": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "controlslist": { - "html": { - "media": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "coords": { - "html": { - "area": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "crossorigin": { - "html": { - "img": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "link": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "media": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "script": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "cursor": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "cx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "cy": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "d": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": true - } - } - }, - "data": { - "html": { - "object": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "datatype": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "datetime": { - "html": { - "del": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "ins": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "time": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "decelerate": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "decoding": { - "html": { - "img": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false, - "default_value": "auto" - } - } - }, - "default": { - "html": { - "track": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "defaultchecked": { - "html": { - "*": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "defaultvalue": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "defer": { - "html": { - "script": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "descent": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "diffuseconstant": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "dir": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "direction": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "dirname": { - "html": { - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "disabled": { - "html": { - "button": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "fieldset": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "input": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "keygen": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "optgroup": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "option": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "select": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "textarea": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "disablepictureinpicture": { - "html": { - "video": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "display": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "divisor": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "dominantbaseline": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "download": { - "html": { - "a": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "area": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "draggable": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "dur": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "dx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "dy": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "edgemode": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "elevation": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "enablebackground": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "enctype": { - "html": { - "form": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "application/x-www-form-urlencoded" - } - } - }, - "end": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "exponent": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "externalresourcesrequired": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "fill": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fillopacity": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fillrule": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "filter": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "filterres": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "filterunits": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "floodcolor": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "floodopacity": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "focusable": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "fontfamily": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fontsize": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fontsizeadjust": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fontstretch": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fontstyle": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fontvariant": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fontweight": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "form": { - "html": { - "button": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "fieldset": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "keygen": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "label": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "meter": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "object": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "output": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "select": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "formaction": { - "html": { - "button": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "format": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "formenctype": { - "html": { - "button": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "application/x-www-form-urlencoded" - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "application/x-www-form-urlencoded" - } - } - }, - "formmethod": { - "html": { - "button": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "formnovalidate": { - "html": { - "button": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "input": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "formtarget": { - "html": { - "button": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "frameborder": { - "html": { - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "1" - } - } - }, - "from": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "fy": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "g1": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "g2": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "glyphname": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "glyphorientationhorizontal": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "glyphorientationvertical": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "glyphref": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "gradienttransform": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "gradientunits": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "hanging": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "headers": { - "html": { - "td": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "th": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "height": { - "html": { - "canvas": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "embed": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "150" - }, - "img": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "object": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "video": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "hidden": { - "html": { - "*": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "high": { - "html": { - "meter": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "horizadvx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "horizoriginx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "href": { - "html": { - "a": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "area": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "base": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "link": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "hreflang": { - "html": { - "a": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "area": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "link": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "htmlfor": { - "html": { - "label": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "output": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "httpequiv": { - "html": { - "meta": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "id": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "ideographic": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "imagerendering": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "in": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "in2": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "inlist": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "inputmode": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "integrity": { - "html": { - "link": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "script": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "intercept": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "is": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "itemid": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "itemprop": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "itemref": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "itemscope": { - "html": { - "*": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "itemtype": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "k": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "k1": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "k2": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "k3": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "k4": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "kernelmatrix": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "kernelunitlength": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "kerning": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "keyparams": { - "html": { - "keygen": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "keypoints": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "keysplines": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "keytimes": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "keytype": { - "html": { - "keygen": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "kind": { - "html": { - "track": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "label": { - "html": { - "optgroup": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "option": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "track": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "lang": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "lengthadjust": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "letterspacing": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "lightingcolor": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "limitingconeangle": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "list": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "local": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "loop": { - "html": { - "media": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "low": { - "html": { - "meter": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "manifest": { - "html": { - "html": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "marginheight": { - "html": { - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "marginwidth": { - "html": { - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "markerend": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "markerheight": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "markermid": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "markerstart": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "markerunits": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "markerwidth": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "mask": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "maskcontentunits": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "maskunits": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "mathematical": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "max": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "meter": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "progress": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "maxlength": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "media": { - "html": { - "a": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "area": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "link": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "source": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "style": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "all" - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "mediagroup": { - "html": { - "media": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "method": { - "html": { - "form": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "get" - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "min": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "meter": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "minlength": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "mode": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "multiple": { - "html": { - "input": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "select": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "muted": { - "html": { - "media": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "name": { - "html": { - "button": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "fieldset": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "form": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "keygen": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "map": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "meta": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "object": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "output": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "param": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "select": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "nomodule": { - "html": { - "script": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "nonce": { - "html": { - "script": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "style": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "novalidate": { - "html": { - "form": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "numoctaves": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "offset": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "onchange": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "select": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "textarea": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "opacity": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "open": { - "html": { - "details": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "dialog": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "operator": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "optimum": { - "html": { - "meter": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "order": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "orient": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "orientation": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "origin": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "overflow": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "overlineposition": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "overlinethickness": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "paintorder": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "panose1": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "pathlength": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "pattern": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "patterncontentunits": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "patterntransform": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "patternunits": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "ping": { - "html": { - "a": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "placeholder": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "playsinline": { - "html": { - "media": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "video": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "pointerevents": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "points": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "pointsatx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "pointsaty": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "pointsatz": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "poster": { - "html": { - "video": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "prefix": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "preload": { - "html": { - "media": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "preservealpha": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "preserveaspectratio": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "primitiveunits": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "property": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "r": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "radiogroup": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "radius": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "readonly": { - "html": { - "input": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "textarea": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "referrerpolicy": { - "html": { - "a": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "no-referrer-when-downgrade" - } - } - }, - "refx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "refy": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "rel": { - "html": { - "a": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "area": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "link": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "renderingintent": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "repeatcount": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "repeatdur": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "required": { - "html": { - "input": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "select": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "textarea": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "requiredextensions": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "requiredfeatures": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "resource": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "restart": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "result": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "results": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "reversed": { - "html": { - "ol": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "role": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "rotate": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "rows": { - "html": { - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "rowspan": { - "html": { - "td": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "th": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "rx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "ry": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "sandbox": { - "html": { - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "scale": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "scope": { - "html": { - "td": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "th": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "scoped": { - "html": { - "style": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "scrolling": { - "html": { - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "seamless": { - "html": { - "iframe": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "security": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "seed": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "selected": { - "html": { - "option": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "shape": { - "html": { - "area": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "shaperendering": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "size": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "select": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "sizes": { - "html": { - "img": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "link": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "source": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "slope": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "slot": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "spacing": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "span": { - "html": { - "col": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "1" - }, - "colgroup": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "1" - } - } - }, - "specularconstant": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "specularexponent": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "speed": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "spellcheck": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "spreadmethod": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "src": { - "html": { - "embed": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "img": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "media": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "script": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "source": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "track": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "srcdoc": { - "html": { - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "srclang": { - "html": { - "track": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "srcset": { - "html": { - "img": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "source": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "start": { - "html": { - "ol": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "startoffset": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "stddeviation": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "stemh": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "stemv": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "step": { - "html": { - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "stitchtiles": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "stopcolor": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "stopopacity": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "strikethroughposition": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "strikethroughthickness": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "string": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "stroke": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "strokedasharray": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "strokedashoffset": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "strokelinecap": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "strokelinejoin": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "strokemiterlimit": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "strokeopacity": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "strokewidth": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "style": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "summary": { - "html": { - "table": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "suppresscontenteditablewarning": { - "html": { - "*": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "suppresshydrationwarning": { - "html": { - "*": { - "boolean": true, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "surfacescale": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "systemlanguage": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "tabindex": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "tablevalues": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "target": { - "html": { - "a": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "_self" - }, - "area": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "base": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "form": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "_self" - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "targetx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "targety": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "textanchor": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "textdecoration": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "textlength": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "textrendering": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "title": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "to": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "transform": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "translate": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "type": { - "html": { - "a": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "button": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false, - "default_value": "submit" - }, - "embed": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "text" - }, - "link": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "text/css" - }, - "menu": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "object": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "ol": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - }, - "script": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "source": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "style": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "text/css" - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "typeof": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "u1": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "u2": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "underlineposition": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "underlinethickness": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "unicode": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "unicodebidi": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "unicoderange": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "unitsperem": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "unselectable": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "usemap": { - "html": { - "img": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "object": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "valign": { - "html": { - "td": { - "boolean": false, - "redundant_if_empty": false, - "collapse_and_trim": false - } - } - }, - "valphabetic": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "value": { - "html": { - "button": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "data": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "li": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "meter": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "option": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "param": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "progress": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "select": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "values": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "vectoreffect": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "version": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "vertadvy": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "vertoriginx": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "vertoriginy": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "vhanging": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "videographic": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "viewbox": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "viewtarget": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "visibility": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "vmathematical": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "vocab": { - "html": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "width": { - "html": { - "canvas": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "col": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "embed": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "iframe": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false, - "default_value": "300" - }, - "img": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "input": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "object": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - }, - "video": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - }, - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "widths": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "wmode": { - "html": { - "object": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "wordspacing": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "wrap": { - "html": { - "textarea": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "writingmode": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "x": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "x1": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "x2": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xchannelselector": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xheight": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xlinkactuate": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xlinkarcrole": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xlinkhref": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xlinkrole": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xlinkshow": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xlinktitle": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xlinktype": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xmlbase": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xmllang": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xmlns": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xmlnsxlink": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "xmlspace": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "y": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "y1": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "y2": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "ychannelselector": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "z": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - }, - "zoomandpan": { - "svg": { - "*": { - "boolean": false, - "redundant_if_empty": true, - "collapse_and_trim": false - } - } - } -} \ No newline at end of file diff --git a/gen/attrs.ts b/gen/attrs.ts new file mode 100644 index 0000000..763df47 --- /dev/null +++ b/gen/attrs.ts @@ -0,0 +1,210 @@ +import {readFileSync, writeFileSync} from 'fs'; +import ts, {Node, SourceFile, SyntaxKind, Type} from 'typescript'; +import {join} from 'path'; +import {DATA_DIR, prettyJson, RUST_OUT_DIR} from './_common'; + +const reactDeclarations = readFileSync(join(__dirname, 'data', 'react.d.ts'), 'utf8'); + +// TODO Consider and check behaviour when value matches case insensitively, after trimming whitespace, numerically (for number values), etc. +// TODO This file is currently manually sourced and written. Try to get machine-readable spec and automate. +const defaultAttributeValues: { + [attr: string]: { + tags: string[]; + defaultValue: string; + isPositiveInteger?: boolean; + }[]; +} = JSON.parse(readFileSync(join(DATA_DIR, 'attrs.json'), 'utf8')); + +const tagNameNormalised = { + 'anchor': 'a', +}; + +const attrNameNormalised = { + 'classname': 'class', +}; + +const reactSpecificAttributes = [ + 'defaultChecked', 'defaultValue', 'suppressContentEditableWarning', 'suppressHydrationWarning', +]; + +const collapsibleAndTrimmable = { + 'class': ['html:*'], + 'd': ['svg:*'], +}; + +// TODO Is escapedText the API for getting name? +const getNameOfNode = (n: any) => n.name.escapedText; +const normaliseName = (name: string, norms: { [name: string]: string }) => [name.toLowerCase()].map(n => norms[n] || n)[0]; + +type AttrConfig = { + boolean: boolean; + redundantIfEmpty: boolean; + collapseAndTrim: boolean; + defaultValue?: string; +}; + +const rsTagAttr = ({ + redundantIfEmpty, + defaultValue, + collapseAndTrim, + boolean, +}: AttrConfig) => `AttributeMinification { + boolean: ${boolean}, + redundant_if_empty: ${redundantIfEmpty}, + collapse_and_trim: ${collapseAndTrim}, + default_value: ${defaultValue == undefined ? 'None' : `Some(b"${defaultValue}")`}, +}`; + +const processReactTypeDeclarations = (source: SourceFile) => { + const nodes: Node[] = [source]; + // Use index-based loop to keep iterating as nodes array grows. + for (let i = 0; i < nodes.length; i++) { + // forEachChild doesn't work if return value is number (e.g. return value of Array.prototype.push). + nodes[i].forEachChild(c => void nodes.push(c)); + } + const attributeNodes = nodes + .filter(n => n.kind === ts.SyntaxKind.InterfaceDeclaration) + .map(n => [/^([A-Za-z]*)(HTML|SVG)Attributes/.exec(getNameOfNode(n)), n]) + .filter(([matches]) => !!matches) + .map(([matches, node]) => [matches![2].toLowerCase(), normaliseName(matches![1], tagNameNormalised), node]) + .filter(([namespace, tagName]) => namespace !== 'html' || !['all', 'webview'].includes(tagName)) + .map(([namespace, tag, node]) => ({namespace, tag, node})) + .sort((a, b) => a.namespace.localeCompare(b.namespace) || a.tag.localeCompare(b.tag)); + + // Process global HTML attributes first as they also appear on some specific HTML tags but we don't want to keep the specific ones if they're global. + if (attributeNodes[0].namespace !== 'html' || attributeNodes[0].tag !== '') { + throw new Error(`Global HTML attributes is not first to be processed`); + } + + // Map structure: attr => namespace => tag => config. + const attributes = new Map>>(); + + for (const {namespace, tag, node} of attributeNodes) { + const fullyQualifiedTagName = [namespace, tag || '*'].join(':'); + for (const n of node.members.filter((n: Node) => n.kind === ts.SyntaxKind.PropertySignature)) { + const attrName = normaliseName(getNameOfNode(n), attrNameNormalised); + if (reactSpecificAttributes.includes(attrName)) continue; + + const types: SyntaxKind[] = n.type.kind === ts.SyntaxKind.UnionType + ? n.type.types.map((t: Node) => t.kind) + : [n.type.kind]; + + const boolean = types.includes(ts.SyntaxKind.BooleanKeyword); + // If types includes boolean and string, make it a boolean attr to prevent it from being removed if empty value. + const redundantIfEmpty = !boolean && types.some(t => t === ts.SyntaxKind.StringKeyword || t === ts.SyntaxKind.NumberKeyword); + const defaultValues = (defaultAttributeValues[attrName] || []) + .filter(a => a.tags.includes(fullyQualifiedTagName)) + .map(a => a.defaultValue); + const collapseAndTrim = (collapsibleAndTrimmable[attrName] || []).includes(fullyQualifiedTagName); + if (defaultValues.length > 1) { + throw new Error(`Tag-attribute combination <${fullyQualifiedTagName} ${attrName}> has multiple default values: ${defaultValues}`); + } + const attr: AttrConfig = { + boolean, + redundantIfEmpty, + collapseAndTrim, + defaultValue: defaultValues[0], + }; + + if (!attributes.has(attrName)) attributes.set(attrName, new Map()); + const namespacesForAttribute = attributes.get(attrName)!; + if (!namespacesForAttribute.has(namespace)) namespacesForAttribute.set(namespace, new Map()); + const tagsForNsAttribute = namespacesForAttribute.get(namespace)!; + if (tagsForNsAttribute.has(tag)) throw new Error(`Duplicate tag-attribute combination: <${fullyQualifiedTagName} ${attrName}>`); + + const globalAttr = tagsForNsAttribute.get('*'); + if (globalAttr) { + if (globalAttr.boolean !== attr.boolean + || globalAttr.redundantIfEmpty !== attr.redundantIfEmpty + || globalAttr.collapseAndTrim !== attr.collapseAndTrim + || globalAttr.defaultValue !== attr.defaultValue) { + throw new Error(`Global and tag-specific attributes conflict: ${prettyJson(globalAttr)} ${prettyJson(attr)}`); + } + } else { + tagsForNsAttribute.set(tag || '*', attr); + } + } + } + + let code = ` +use crate::spec::tag::ns::Namespace; + +pub struct AttributeMinification { + pub boolean: bool, + pub redundant_if_empty: bool, + pub collapse_and_trim: bool, + pub default_value: Option<&'static [u8]>, +} + +pub enum AttrMapEntry { + AllNamespaceElements(AttributeMinification), + SpecificNamespaceElements(phf::Map<&'static [u8], AttributeMinification>), +} + +#[derive(Clone, Copy)] +pub struct ByNamespace { + // Make pub so this struct can be statically created in gen/attrs.rs. + pub html: Option<&'static AttrMapEntry>, + pub svg: Option<&'static AttrMapEntry>, +} + +impl ByNamespace { + fn get(&self, ns: Namespace) -> Option<&'static AttrMapEntry> { + match ns { + Namespace::Html => self.html, + Namespace::Svg => self.svg, + } + } +} + +pub struct AttrMap(phf::Map<&'static [u8], ByNamespace>); + +impl AttrMap { + pub const fn new(map: phf::Map<&'static [u8], ByNamespace>) -> AttrMap { + AttrMap(map) + } + + pub fn get(&self, ns: Namespace, tag: &[u8], attr: &[u8]) -> Option<&AttributeMinification> { + self.0.get(attr).and_then(|namespaces| namespaces.get(ns)).and_then(|entry| match entry { + AttrMapEntry::AllNamespaceElements(min) => Some(min), + AttrMapEntry::SpecificNamespaceElements(map) => map.get(tag), + }) + } +} + +`; + + for (const [attrName, namespaces] of attributes) { + let byNsCode = ''; + byNsCode += `static ${attrName.toUpperCase()}_ATTR: ByNamespace = ByNamespace {\n`; + for (const ns of ['html', 'svg'] as const) { + byNsCode += `\t${ns}: `; + const tagsMap = namespaces.get(ns); + if (!tagsMap) { + byNsCode += 'None'; + } else { + const globalAttr = tagsMap.get('*'); + if (globalAttr) { + code += `static ${ns.toUpperCase()}_${attrName.toUpperCase()}_ATTR: &AttrMapEntry = &AttrMapEntry::AllNamespaceElements(${rsTagAttr(globalAttr)});\n\n`; + } else { + code += `static ${ns.toUpperCase()}_${attrName.toUpperCase()}_ATTR: &AttrMapEntry = &AttrMapEntry::SpecificNamespaceElements(phf::phf_map! {\n${ + [...tagsMap].map(([tagName, tagAttr]) => `b\"${tagName}\" => ${rsTagAttr(tagAttr)}`).join(',\n') + }\n});\n\n`; + } + byNsCode += `Some(${ns.toUpperCase()}_${attrName.toUpperCase()}_ATTR)`; + } + byNsCode += ',\n'; + } + byNsCode += '};\n\n'; + code += byNsCode; + } + code += 'pub static ATTRS: AttrMap = AttrMap::new(phf::phf_map! {\n'; + for (const attr_name of attributes.keys()) { + code += `\tb\"${attr_name}\" => ${attr_name.toUpperCase()}_ATTR,\n`; + } + code += '});\n\n'; + return code; +}; + +const source = ts.createSourceFile(`react.d.ts`, reactDeclarations, ts.ScriptTarget.ES2020); +writeFileSync(join(RUST_OUT_DIR, 'attrs.rs'), processReactTypeDeclarations(source)); diff --git a/gen/build/attrs.js b/gen/build/attrs.js deleted file mode 100644 index e03cffe..0000000 --- a/gen/build/attrs.js +++ /dev/null @@ -1,215 +0,0 @@ -const request = require('request-promise-native'); -const {promises: fs} = require('fs'); -const ts = require('typescript'); -const path = require('path'); - -const compareEntryNames = (a, b) => a[0].localeCompare(b[0]); -const deepObjectifyMap = map => Object.fromEntries( - [...map.entries()] - .map(([key, value]) => [key, value instanceof Map ? deepObjectifyMap(value) : value]) - .sort(compareEntryNames) -); -const fromCamelCase = camelCase => camelCase.split(/(?=^|[A-Z])/).map(w => w.toLowerCase()); -const prettyjson = v => JSON.stringify(v, null, 2); - -const ATTRS_PATH = path.join(__dirname, '..', 'attrs.json'); - -const REACT_TYPINGS_URL = 'https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/master/types/react/index.d.ts'; -const REACT_TYPINGS_FILE = path.join(__dirname, 'react.d.ts'); -const fetchReactTypingsSource = async () => { - try { - return await fs.readFile(REACT_TYPINGS_FILE, 'utf8'); - } catch (err) { - if (err.code !== 'ENOENT') { - throw err; - } - const source = await request(REACT_TYPINGS_URL); - await fs.writeFile(REACT_TYPINGS_FILE, source); - return source; - } -}; - -const tagNameNormalised = { - 'anchor': 'a', -}; - -const attrNameNormalised = { - 'classname': 'class', -}; - -const reactSpecificAttributes = [ - 'defaultChecked', 'defaultValue', 'suppressContentEditableWarning', 'suppressHydrationWarning', -]; - -// TODO Consider and check behaviour when value matches case insensitively, after trimming whitespace, numerically (for number values), etc. -// TODO This is currently manually sourced and written. Try to get machine-readable spec and automate. -const defaultAttributeValues = { - 'align': [{ - tags: ['html:img'], - defaultValue: 'bottom', - }], - 'decoding': [{ - tags: ['html:img'], - defaultValue: 'auto', - }], - 'enctype': [{ - tags: ['html:form'], - defaultValue: 'application/x-www-form-urlencoded', - }], - 'frameborder': [{ - tags: ['html:iframe'], - defaultValue: '1', - isPositiveInteger: true, - }], - 'formenctype': [{ - tags: ['html:button', 'html:input'], - defaultValue: 'application/x-www-form-urlencoded', - }], - 'height': [{ - tags: ['html:iframe'], - defaultValue: '150', - isPositiveInteger: true, - }], - 'importance': [{ - tags: ['html:iframe'], - defaultValue: 'auto', - }], - 'loading': [{ - tags: ['html:iframe', 'html:img'], - defaultValue: 'eager', - }], - 'media': [{ - tags: ['html:style'], - defaultValue: 'all', - }], - 'method': [{ - tags: ['html:form'], - defaultValue: 'get', - }], - 'referrerpolicy': [{ - tags: ['html:iframe', 'html:img'], - defaultValue: 'no-referrer-when-downgrade', - }], - 'rules': [{ - tags: ['html:table'], - defaultValue: 'none', - }], - 'shape': [{ - tags: ['html:area'], - defaultValue: 'rect', - }], - 'span': [{ - tags: ['html:col', 'html:colgroup'], - defaultValue: '1', - isPositiveInteger: true, - }], - 'target': [{ - tags: ['html:a', 'html:form'], - defaultValue: '_self', - }], - 'type': [{ - tags: ['html:button'], - defaultValue: 'submit', - }, { - tags: ['html:input'], - defaultValue: 'text', - }, { - tags: ['html:link', 'html:style'], - defaultValue: 'text/css', - }], - 'width': [{ - tags: ['html:iframe'], - defaultValue: '300', - isPositiveInteger: true, - }] -}; - -const collapsibleAndTrimmable = { - 'class': ['html:*'], - 'd': ['svg:*'], -}; - -// TODO Is escapedText the API for getting name? -const getNameOfNode = n => n.name.escapedText; -const normaliseName = (name, norms) => [name.toLowerCase()].map(n => norms[n] || n)[0]; - -const processReactTypeDeclarations = async (source) => { - const nodes = [source]; - // Use index-based loop to keep iterating as nodes array grows. - for (let i = 0; i < nodes.length; i++) { - // forEachChild doesn't work if return value is number (e.g. return value of Array.prototype.push). - nodes[i].forEachChild(c => void nodes.push(c)); - } - const attributeNodes = nodes - .filter(n => n.kind === ts.SyntaxKind.InterfaceDeclaration) - .map(n => [/^([A-Za-z]*)(HTML|SVG)Attributes/.exec(getNameOfNode(n)), n]) - .filter(([matches]) => matches) - .map(([matches, node]) => [matches[2].toLowerCase(), normaliseName(matches[1], tagNameNormalised), node]) - .filter(([namespace, tagName]) => namespace !== 'html' || !['all', 'webview'].includes(tagName)) - .map(([namespace, tag, node]) => ({namespace, tag, node})) - .sort((a, b) => a.namespace.localeCompare(b.namespace) || a.tag.localeCompare(b.tag)); - - // Process global HTML attributes first as they also appear on some specific HTML tags but we don't want to keep the specific ones if they're global. - if (attributeNodes[0].namespace !== 'html' || attributeNodes[0].tag !== '') { - throw new Error(`Global HTML attributes is not first to be processed`); - } - - // Map structure: attr => namespace => tag => config. - const attributes = new Map(); - - for (const {namespace, tag, node} of attributeNodes) { - const fullyQualifiedTagName = [namespace, tag || '*'].join(':'); - for (const n of node.members.filter(n => n.kind === ts.SyntaxKind.PropertySignature)) { - const attrName = normaliseName(getNameOfNode(n), attrNameNormalised); - if (reactSpecificAttributes.includes(attrName)) continue; - - const types = n.type.kind === ts.SyntaxKind.UnionType - ? n.type.types.map(t => t.kind) - : [n.type.kind]; - - const boolean = types.includes(ts.SyntaxKind.BooleanKeyword); - // If types includes boolean and string, make it a boolean attr to prevent it from being removed if empty value. - const redundantIfEmpty = !boolean && - (types.includes(ts.SyntaxKind.StringKeyword) || types.includes(ts.SyntaxKind.NumberKeyword)); - const defaultValue = (defaultAttributeValues[attrName] || []) - .filter(a => a.tags.includes(fullyQualifiedTagName)) - .map(a => a.defaultValue); - const collapseAndTrim = (collapsibleAndTrimmable[attrName] || []).includes(fullyQualifiedTagName); - if (defaultValue.length > 1) { - throw new Error(`Tag-attribute combination <${fullyQualifiedTagName} ${attrName}> has multiple default values: ${defaultValue}`); - } - const attr = { - boolean, - redundant_if_empty: redundantIfEmpty, - collapse_and_trim: collapseAndTrim, - default_value: defaultValue[0], - }; - - if (!attributes.has(attrName)) attributes.set(attrName, new Map()); - const namespacesForAttribute = attributes.get(attrName); - if (!namespacesForAttribute.has(namespace)) namespacesForAttribute.set(namespace, new Map()); - const tagsForNSAttribute = namespacesForAttribute.get(namespace); - if (tagsForNSAttribute.has(tag)) throw new Error(`Duplicate tag-attribute combination: <${fullyQualifiedTagName} ${attrName}>`); - - const globalAttr = tagsForNSAttribute.get('*'); - if (globalAttr) { - if (globalAttr.boolean !== attr.boolean - || globalAttr.redundant_if_empty !== attr.redundant_if_empty - || globalAttr.collapse_and_trim !== attr.collapse_and_trim - || globalAttr.default_value !== attr.default_value) { - throw new Error(`Global and tag-specific attributes conflict: ${prettyjson(globalAttr)} ${prettyjson(attr)}`); - } - } else { - tagsForNSAttribute.set(tag || '*', attr); - } - } - } - - // Sort output JSON object by property so diffs are clearer. - await fs.writeFile(ATTRS_PATH, prettyjson(deepObjectifyMap(attributes))); -}; - -(async () => { - const source = ts.createSourceFile(`react.d.ts`, await fetchReactTypingsSource(), ts.ScriptTarget.ES2019); - await processReactTypeDeclarations(source); -})(); diff --git a/gen/build/package.json b/gen/build/package.json deleted file mode 100644 index a408f16..0000000 --- a/gen/build/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "private": true, - "dependencies": { - "request": "^2.88.0", - "request-promise-native": "^1.0.8", - "typescript": "^3.7.4" - } -} diff --git a/gen/data/attrs.json b/gen/data/attrs.json new file mode 100644 index 0000000..2bb3548 --- /dev/null +++ b/gen/data/attrs.json @@ -0,0 +1,160 @@ +{ + "align": [ + { + "tags": [ + "html:img" + ], + "defaultValue": "bottom" + } + ], + "decoding": [ + { + "tags": [ + "html:img" + ], + "defaultValue": "auto" + } + ], + "enctype": [ + { + "tags": [ + "html:form" + ], + "defaultValue": "application/x-www-form-urlencoded" + } + ], + "frameborder": [ + { + "tags": [ + "html:iframe" + ], + "defaultValue": "1", + "isPositiveInteger": true + } + ], + "formenctype": [ + { + "tags": [ + "html:button", + "html:input" + ], + "defaultValue": "application/x-www-form-urlencoded" + } + ], + "height": [ + { + "tags": [ + "html:iframe" + ], + "defaultValue": "150", + "isPositiveInteger": true + } + ], + "importance": [ + { + "tags": [ + "html:iframe" + ], + "defaultValue": "auto" + } + ], + "loading": [ + { + "tags": [ + "html:iframe", + "html:img" + ], + "defaultValue": "eager" + } + ], + "media": [ + { + "tags": [ + "html:style" + ], + "defaultValue": "all" + } + ], + "method": [ + { + "tags": [ + "html:form" + ], + "defaultValue": "get" + } + ], + "referrerpolicy": [ + { + "tags": [ + "html:iframe", + "html:img" + ], + "defaultValue": "no-referrer-when-downgrade" + } + ], + "rules": [ + { + "tags": [ + "html:table" + ], + "defaultValue": "none" + } + ], + "shape": [ + { + "tags": [ + "html:area" + ], + "defaultValue": "rect" + } + ], + "span": [ + { + "tags": [ + "html:col", + "html:colgroup" + ], + "defaultValue": "1", + "isPositiveInteger": true + } + ], + "target": [ + { + "tags": [ + "html:a", + "html:form" + ], + "defaultValue": "_self" + } + ], + "type": [ + { + "tags": [ + "html:button" + ], + "defaultValue": "submit" + }, + { + "tags": [ + "html:input" + ], + "defaultValue": "text" + }, + { + "tags": [ + "html:link", + "html:style" + ], + "defaultValue": "text/css" + } + ], + "width": [ + { + "tags": [ + "html:iframe" + ], + "defaultValue": "300", + "isPositiveInteger": true + } + ] +} diff --git a/gen/data/dfa.yaml b/gen/data/dfa.yaml new file mode 100644 index 0000000..e93462d --- /dev/null +++ b/gen/data/dfa.yaml @@ -0,0 +1,77 @@ +# Prefixes: +# `_` means to lowercase accumulate. +# `<` means to accumulate transition pattern as part of current state. +# `+` means to accumulate transition pattern as part of next state. +# `?` means to look ahead but don't accumulate transition pattern and allow next state to reconsume. + +Text: + '\w': ?TextWhitespace + '\<': +OpeningTagStart + '\': ': _ClosingTag + '>': ': _OpeningTagStart + +OpeningTagWhitespace: + '\w': OpeningTagWhitespace + '': ?AttrName + '>': =\w]': ?AttrAfterName + '': _AttrName + +AttrAfterName: + '\w': AttrAfterName + '>': ?OpeningTagWhitespace + '=': +AttrBeforeValue + +AttrBeforeValue: + '\w': AttrBeforeValue + "'": +AttrSingleQuotedValue + '"': +AttrDoubleQuotedValue + '': ?AttrUnquotedValue + +AttrSingleQuotedValue: + "'": +// AssureSign +// Microsoft +// John Reilly +// Benoit Benezech +// Patricio Zavolinsky +// Digiguru +// Eric Anderson +// Dovydas Navickas +// Josh Rutherford +// Guilherme Hübner +// Ferdy Budhidharma +// Johann Rakotoharisoa +// Olivier Pascal +// Martin Hochel +// Frank Li +// Jessica Franco +// Saransh Kataria +// Kanitkorn Sujautra +// Sebastian Silbermann +// Kyle Scully +// Cong Zhang +// Dimitri Mitropoulos +// JongChan Choi +// Victor Magalhães +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// TypeScript Version: 2.8 + +// NOTE: Users of the `experimental` builds of React should add a reference +// to 'react/experimental' in their project. See experimental.d.ts's top comment +// for reference and documentation on how exactly to do it. + +/// + +import * as CSS from 'csstype'; +import * as PropTypes from 'prop-types'; + +type NativeAnimationEvent = AnimationEvent; +type NativeClipboardEvent = ClipboardEvent; +type NativeCompositionEvent = CompositionEvent; +type NativeDragEvent = DragEvent; +type NativeFocusEvent = FocusEvent; +type NativeKeyboardEvent = KeyboardEvent; +type NativeMouseEvent = MouseEvent; +type NativeTouchEvent = TouchEvent; +type NativePointerEvent = PointerEvent; +type NativeTransitionEvent = TransitionEvent; +type NativeUIEvent = UIEvent; +type NativeWheelEvent = WheelEvent; +type Booleanish = boolean | 'true' | 'false'; + +/** + * defined in scheduler/tracing + */ +interface SchedulerInteraction { + id: number; + name: string; + timestamp: number; +} + +// tslint:disable-next-line:export-just-namespace +export = React; +export as namespace React; + +declare namespace React { + // + // React Elements + // ---------------------------------------------------------------------- + + type ElementType

= + { + [K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never + }[keyof JSX.IntrinsicElements] | + ComponentType

; + /** + * @deprecated Please use `ElementType` + */ + type ReactType

= ElementType

; + type ComponentType

= ComponentClass

| FunctionComponent

; + + type JSXElementConstructor

= + | ((props: P) => ReactElement | null) + | (new (props: P) => Component); + + interface RefObject { + readonly current: T | null; + } + type RefCallback = { bivarianceHack(instance: T | null): void }["bivarianceHack"]; + type Ref = RefCallback | RefObject | null; + type LegacyRef = string | Ref; + /** + * Gets the instance type for a React element. The instance will be different for various component types: + * + * - React class components will be the class instance. So if you had `class Foo extends React.Component<{}> {}` + * and used `React.ElementRef` then the type would be the instance of `Foo`. + * - React stateless functional components do not have a backing instance and so `React.ElementRef` + * (when `Bar` is `function Bar() {}`) will give you the `undefined` type. + * - JSX intrinsics like `div` will give you their DOM instance. For `React.ElementRef<'div'>` that would be + * `HTMLDivElement`. For `React.ElementRef<'input'>` that would be `HTMLInputElement`. + * - React stateless functional components that forward a `ref` will give you the `ElementRef` of the forwarded + * to component. + * + * `C` must be the type _of_ a React component so you need to use typeof as in React.ElementRef. + * + * @todo In Flow, this works a little different with forwarded refs and the `AbstractComponent` that + * `React.forwardRef()` returns. + */ + type ElementRef< + C extends + | ForwardRefExoticComponent + | { new (props: any): Component } + | ((props: any, context?: any) => ReactElement | null) + | keyof JSX.IntrinsicElements + > = C extends ForwardRefExoticComponent + ? FP extends RefAttributes + ? FC + : never + : C extends { new (props: any): Component } + ? InstanceType + : C extends ((props: any, context?: any) => ReactElement | null) + ? undefined + : C extends keyof JSX.IntrinsicElements + ? JSX.IntrinsicElements[C] extends DOMAttributes + ? E + : never + : never; + + type ComponentState = any; + + type Key = string | number; + + /** + * @internal You shouldn't need to use this type since you never see these attributes + * inside your component or have to validate them. + */ + interface Attributes { + key?: Key; + } + interface RefAttributes extends Attributes { + ref?: Ref; + } + interface ClassAttributes extends Attributes { + ref?: LegacyRef; + } + + interface ReactElement

= string | JSXElementConstructor> { + type: T; + props: P; + key: Key | null; + } + + interface ReactComponentElement< + T extends keyof JSX.IntrinsicElements | JSXElementConstructor, + P = Pick, Exclude, 'key' | 'ref'>> + > extends ReactElement> { } + + /** + * @deprecated Please use `FunctionComponentElement` + */ + type SFCElement

= FunctionComponentElement

; + + interface FunctionComponentElement

extends ReactElement> { + ref?: 'ref' extends keyof P ? P extends { ref?: infer R } ? R : never : never; + } + + type CElement> = ComponentElement; + interface ComponentElement> extends ReactElement> { + ref?: LegacyRef; + } + + type ClassicElement

= CElement>; + + // string fallback for custom web-components + interface DOMElement

| SVGAttributes, T extends Element> extends ReactElement { + ref: LegacyRef; + } + + // ReactHTML for ReactHTMLElement + // tslint:disable-next-line:no-empty-interface + interface ReactHTMLElement extends DetailedReactHTMLElement, T> { } + + interface DetailedReactHTMLElement

, T extends HTMLElement> extends DOMElement { + type: keyof ReactHTML; + } + + // ReactSVG for ReactSVGElement + interface ReactSVGElement extends DOMElement, SVGElement> { + type: keyof ReactSVG; + } + + interface ReactPortal extends ReactElement { + key: Key | null; + children: ReactNode; + } + + // + // Factories + // ---------------------------------------------------------------------- + + type Factory

= (props?: Attributes & P, ...children: ReactNode[]) => ReactElement

; + + /** + * @deprecated Please use `FunctionComponentFactory` + */ + type SFCFactory

= FunctionComponentFactory

; + + type FunctionComponentFactory

= (props?: Attributes & P, ...children: ReactNode[]) => FunctionComponentElement

; + + type ComponentFactory> = + (props?: ClassAttributes & P, ...children: ReactNode[]) => CElement; + + type CFactory> = ComponentFactory; + type ClassicFactory

= CFactory>; + + type DOMFactory

, T extends Element> = + (props?: ClassAttributes & P | null, ...children: ReactNode[]) => DOMElement; + + // tslint:disable-next-line:no-empty-interface + interface HTMLFactory extends DetailedHTMLFactory, T> {} + + interface DetailedHTMLFactory

, T extends HTMLElement> extends DOMFactory { + (props?: ClassAttributes & P | null, ...children: ReactNode[]): DetailedReactHTMLElement; + } + + interface SVGFactory extends DOMFactory, SVGElement> { + (props?: ClassAttributes & SVGAttributes | null, ...children: ReactNode[]): ReactSVGElement; + } + + // + // React Nodes + // http://facebook.github.io/react/docs/glossary.html + // ---------------------------------------------------------------------- + + type ReactText = string | number; + type ReactChild = ReactElement | ReactText; + + interface ReactNodeArray extends Array {} + type ReactFragment = {} | ReactNodeArray; + type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined; + + // + // Top Level API + // ---------------------------------------------------------------------- + + // DOM Elements + function createFactory( + type: keyof ReactHTML): HTMLFactory; + function createFactory( + type: keyof ReactSVG): SVGFactory; + function createFactory

, T extends Element>( + type: string): DOMFactory; + + // Custom components + function createFactory

(type: FunctionComponent

): FunctionComponentFactory

; + function createFactory

( + type: ClassType, ClassicComponentClass

>): CFactory>; + function createFactory, C extends ComponentClass

>( + type: ClassType): CFactory; + function createFactory

(type: ComponentClass

): Factory

; + + // DOM Elements + // TODO: generalize this to everything in `keyof ReactHTML`, not just "input" + function createElement( + type: "input", + props?: InputHTMLAttributes & ClassAttributes | null, + ...children: ReactNode[]): DetailedReactHTMLElement, HTMLInputElement>; + function createElement

, T extends HTMLElement>( + type: keyof ReactHTML, + props?: ClassAttributes & P | null, + ...children: ReactNode[]): DetailedReactHTMLElement; + function createElement

, T extends SVGElement>( + type: keyof ReactSVG, + props?: ClassAttributes & P | null, + ...children: ReactNode[]): ReactSVGElement; + function createElement

, T extends Element>( + type: string, + props?: ClassAttributes & P | null, + ...children: ReactNode[]): DOMElement; + + // Custom components + + function createElement

( + type: FunctionComponent

, + props?: Attributes & P | null, + ...children: ReactNode[]): FunctionComponentElement

; + function createElement

( + type: ClassType, ClassicComponentClass

>, + props?: ClassAttributes> & P | null, + ...children: ReactNode[]): CElement>; + function createElement

, C extends ComponentClass

>( + type: ClassType, + props?: ClassAttributes & P | null, + ...children: ReactNode[]): CElement; + function createElement

( + type: FunctionComponent

| ComponentClass

| string, + props?: Attributes & P | null, + ...children: ReactNode[]): ReactElement

; + + // DOM Elements + // ReactHTMLElement + function cloneElement

, T extends HTMLElement>( + element: DetailedReactHTMLElement, + props?: P, + ...children: ReactNode[]): DetailedReactHTMLElement; + // ReactHTMLElement, less specific + function cloneElement

, T extends HTMLElement>( + element: ReactHTMLElement, + props?: P, + ...children: ReactNode[]): ReactHTMLElement; + // SVGElement + function cloneElement

, T extends SVGElement>( + element: ReactSVGElement, + props?: P, + ...children: ReactNode[]): ReactSVGElement; + // DOM Element (has to be the last, because type checking stops at first overload that fits) + function cloneElement

, T extends Element>( + element: DOMElement, + props?: DOMAttributes & P, + ...children: ReactNode[]): DOMElement; + + // Custom components + function cloneElement

( + element: FunctionComponentElement

, + props?: Partial

& Attributes, + ...children: ReactNode[]): FunctionComponentElement

; + function cloneElement>( + element: CElement, + props?: Partial

& ClassAttributes, + ...children: ReactNode[]): CElement; + function cloneElement

( + element: ReactElement

, + props?: Partial

& Attributes, + ...children: ReactNode[]): ReactElement

; + + // Context via RenderProps + interface ProviderProps { + value: T; + children?: ReactNode; + } + + interface ConsumerProps { + children: (value: T) => ReactNode; + unstable_observedBits?: number; + } + + // TODO: similar to how Fragment is actually a symbol, the values returned from createContext, + // forwardRef and memo are actually objects that are treated specially by the renderer; see: + // https://github.com/facebook/react/blob/v16.6.0/packages/react/src/ReactContext.js#L35-L48 + // https://github.com/facebook/react/blob/v16.6.0/packages/react/src/forwardRef.js#L42-L45 + // https://github.com/facebook/react/blob/v16.6.0/packages/react/src/memo.js#L27-L31 + // However, we have no way of telling the JSX parser that it's a JSX element type or its props other than + // by pretending to be a normal component. + // + // We don't just use ComponentType or SFC types because you are not supposed to attach statics to this + // object, but rather to the original function. + interface ExoticComponent

{ + /** + * **NOTE**: Exotic components are not callable. + */ + (props: P): (ReactElement|null); + readonly $$typeof: symbol; + } + + interface NamedExoticComponent

extends ExoticComponent

{ + displayName?: string; + } + + interface ProviderExoticComponent

extends ExoticComponent

{ + propTypes?: WeakValidationMap

; + } + + type ContextType> = C extends Context ? T : never; + + // NOTE: only the Context object itself can get a displayName + // https://github.com/facebook/react-devtools/blob/e0b854e4c/backend/attachRendererFiber.js#L310-L325 + type Provider = ProviderExoticComponent>; + type Consumer = ExoticComponent>; + interface Context { + Provider: Provider; + Consumer: Consumer; + displayName?: string; + } + function createContext( + // If you thought this should be optional, see + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24509#issuecomment-382213106 + defaultValue: T, + calculateChangedBits?: (prev: T, next: T) => number + ): Context; + + function isValidElement

(object: {} | null | undefined): object is ReactElement

; + + const Children: ReactChildren; + const Fragment: ExoticComponent<{ children?: ReactNode }>; + const StrictMode: ExoticComponent<{ children?: ReactNode }>; + + interface SuspenseProps { + children?: ReactNode; + + /** A fallback react tree to show when a Suspense child (like React.lazy) suspends */ + fallback: NonNullable|null; + /** + * Tells React whether to “skip” revealing this boundary during the initial load. + * This API will likely be removed in a future release. + */ + // NOTE: this is unflagged and is respected even in stable builds + unstable_avoidThisFallback?: boolean; + } + /** + * This feature is not yet available for server-side rendering. + * Suspense support will be added in a later release. + */ + const Suspense: ExoticComponent; + const version: string; + + /** + * {@link https://github.com/bvaughn/rfcs/blob/profiler/text/0000-profiler.md#detailed-design | API} + */ + type ProfilerOnRenderCallback = ( + id: string, + phase: "mount" | "update", + actualDuration: number, + baseDuration: number, + startTime: number, + commitTime: number, + interactions: Set, + ) => void; + interface ProfilerProps { + children?: ReactNode; + id: string; + onRender: ProfilerOnRenderCallback; + } + + const Profiler: ExoticComponent; + + // + // Component API + // ---------------------------------------------------------------------- + + type ReactInstance = Component | Element; + + // Base component for plain JS classes + // tslint:disable-next-line:no-empty-interface + interface Component

extends ComponentLifecycle { } + class Component { + // tslint won't let me format the sample code in a way that vscode likes it :( + /** + * If set, `this.context` will be set at runtime to the current value of the given Context. + * + * Usage: + * + * ```ts + * type MyContext = number + * const Ctx = React.createContext(0) + * + * class Foo extends React.Component { + * static contextType = Ctx + * context!: React.ContextType + * render () { + * return <>My context's value: {this.context}; + * } + * } + * ``` + * + * @see https://reactjs.org/docs/context.html#classcontexttype + */ + static contextType?: Context; + + /** + * If using the new style context, re-declare this in your class to be the + * `React.ContextType` of your `static contextType`. + * Should be used with type annotation or static contextType. + * + * ```ts + * static contextType = MyContext + * // For TS pre-3.7: + * context!: React.ContextType + * // For TS 3.7 and above: + * declare context: React.ContextType + * ``` + * + * @see https://reactjs.org/docs/context.html + */ + // TODO (TypeScript 3.0): unknown + context: any; + + constructor(props: Readonly

); + /** + * @deprecated + * @see https://reactjs.org/docs/legacy-context.html + */ + constructor(props: P, context?: any); + + // We MUST keep setState() as a unified signature because it allows proper checking of the method return type. + // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257 + // Also, the ` | S` allows intellisense to not be dumbisense + setState( + state: ((prevState: Readonly, props: Readonly

) => (Pick | S | null)) | (Pick | S | null), + callback?: () => void + ): void; + + forceUpdate(callback?: () => void): void; + render(): ReactNode; + + // React.Props is now deprecated, which means that the `children` + // property is not available on `P` by default, even though you can + // always pass children as variadic arguments to `createElement`. + // In the future, if we can define its call signature conditionally + // on the existence of `children` in `P`, then we should remove this. + readonly props: Readonly

& Readonly<{ children?: ReactNode }>; + state: Readonly; + /** + * @deprecated + * https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs + */ + refs: { + [key: string]: ReactInstance + }; + } + + class PureComponent

extends Component { } + + interface ClassicComponent

extends Component { + replaceState(nextState: S, callback?: () => void): void; + isMounted(): boolean; + getInitialState?(): S; + } + + interface ChildContextProvider { + getChildContext(): CC; + } + + // + // Class Interfaces + // ---------------------------------------------------------------------- + + /** + * @deprecated as of recent React versions, function components can no + * longer be considered 'stateless'. Please use `FunctionComponent` instead. + * + * @see [React Hooks](https://reactjs.org/docs/hooks-intro.html) + */ + type SFC

= FunctionComponent

; + + /** + * @deprecated as of recent React versions, function components can no + * longer be considered 'stateless'. Please use `FunctionComponent` instead. + * + * @see [React Hooks](https://reactjs.org/docs/hooks-intro.html) + */ + type StatelessComponent

= FunctionComponent

; + + type FC

= FunctionComponent

; + + interface FunctionComponent

{ + (props: PropsWithChildren

, context?: any): ReactElement | null; + propTypes?: WeakValidationMap

; + contextTypes?: ValidationMap; + defaultProps?: Partial

; + displayName?: string; + } + + interface ForwardRefRenderFunction { + (props: PropsWithChildren

, ref: ((instance: T | null) => void) | MutableRefObject | null): ReactElement | null; + displayName?: string; + // explicit rejected with `never` required due to + // https://github.com/microsoft/TypeScript/issues/36826 + /** + * defaultProps are not supported on render functions + */ + defaultProps?: never; + /** + * propTypes are not supported on render functions + */ + propTypes?: never; + } + + /** + * @deprecated Use ForwardRefRenderFunction. forwardRef doesn't accept a + * "real" component. + */ + interface RefForwardingComponent extends ForwardRefRenderFunction {} + + interface ComponentClass

extends StaticLifecycle { + new (props: P, context?: any): Component; + propTypes?: WeakValidationMap

; + contextType?: Context; + contextTypes?: ValidationMap; + childContextTypes?: ValidationMap; + defaultProps?: Partial

; + displayName?: string; + } + + interface ClassicComponentClass

extends ComponentClass

{ + new (props: P, context?: any): ClassicComponent; + getDefaultProps?(): P; + } + + /** + * We use an intersection type to infer multiple type parameters from + * a single argument, which is useful for many top-level API defs. + * See https://github.com/Microsoft/TypeScript/issues/7234 for more info. + */ + type ClassType, C extends ComponentClass

> = + C & + (new (props: P, context?: any) => T); + + // + // Component Specs and Lifecycle + // ---------------------------------------------------------------------- + + // This should actually be something like `Lifecycle | DeprecatedLifecycle`, + // as React will _not_ call the deprecated lifecycle methods if any of the new lifecycle + // methods are present. + interface ComponentLifecycle extends NewLifecycle, DeprecatedLifecycle { + /** + * Called immediately after a component is mounted. Setting state here will trigger re-rendering. + */ + componentDidMount?(): void; + /** + * Called to determine whether the change in props and state should trigger a re-render. + * + * `Component` always returns true. + * `PureComponent` implements a shallow comparison on props and state and returns true if any + * props or states have changed. + * + * If false is returned, `Component#render`, `componentWillUpdate` + * and `componentDidUpdate` will not be called. + */ + shouldComponentUpdate?(nextProps: Readonly

, nextState: Readonly, nextContext: any): boolean; + /** + * Called immediately before a component is destroyed. Perform any necessary cleanup in this method, such as + * cancelled network requests, or cleaning up any DOM elements created in `componentDidMount`. + */ + componentWillUnmount?(): void; + /** + * Catches exceptions generated in descendant components. Unhandled exceptions will cause + * the entire component tree to unmount. + */ + componentDidCatch?(error: Error, errorInfo: ErrorInfo): void; + } + + // Unfortunately, we have no way of declaring that the component constructor must implement this + interface StaticLifecycle { + getDerivedStateFromProps?: GetDerivedStateFromProps; + getDerivedStateFromError?: GetDerivedStateFromError; + } + + type GetDerivedStateFromProps = + /** + * Returns an update to a component's state based on its new props and old state. + * + * Note: its presence prevents any of the deprecated lifecycle methods from being invoked + */ + (nextProps: Readonly

, prevState: S) => Partial | null; + + type GetDerivedStateFromError = + /** + * This lifecycle is invoked after an error has been thrown by a descendant component. + * It receives the error that was thrown as a parameter and should return a value to update state. + * + * Note: its presence prevents any of the deprecated lifecycle methods from being invoked + */ + (error: any) => Partial | null; + + // This should be "infer SS" but can't use it yet + interface NewLifecycle { + /** + * Runs before React applies the result of `render` to the document, and + * returns an object to be given to componentDidUpdate. Useful for saving + * things such as scroll position before `render` causes changes to it. + * + * Note: the presence of getSnapshotBeforeUpdate prevents any of the deprecated + * lifecycle events from running. + */ + getSnapshotBeforeUpdate?(prevProps: Readonly

, prevState: Readonly): SS | null; + /** + * Called immediately after updating occurs. Not called for the initial render. + * + * The snapshot is only present if getSnapshotBeforeUpdate is present and returns non-null. + */ + componentDidUpdate?(prevProps: Readonly

, prevState: Readonly, snapshot?: SS): void; + } + + interface DeprecatedLifecycle { + /** + * Called immediately before mounting occurs, and before `Component#render`. + * Avoid introducing any side-effects or subscriptions in this method. + * + * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps + * prevents this from being invoked. + * + * @deprecated 16.3, use componentDidMount or the constructor instead; will stop working in React 17 + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#initializing-state + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path + */ + componentWillMount?(): void; + /** + * Called immediately before mounting occurs, and before `Component#render`. + * Avoid introducing any side-effects or subscriptions in this method. + * + * This method will not stop working in React 17. + * + * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps + * prevents this from being invoked. + * + * @deprecated 16.3, use componentDidMount or the constructor instead + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#initializing-state + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path + */ + UNSAFE_componentWillMount?(): void; + /** + * Called when the component may be receiving new props. + * React may call this even if props have not changed, so be sure to compare new and existing + * props if you only want to handle changes. + * + * Calling `Component#setState` generally does not trigger this method. + * + * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps + * prevents this from being invoked. + * + * @deprecated 16.3, use static getDerivedStateFromProps instead; will stop working in React 17 + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#updating-state-based-on-props + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path + */ + componentWillReceiveProps?(nextProps: Readonly

, nextContext: any): void; + /** + * Called when the component may be receiving new props. + * React may call this even if props have not changed, so be sure to compare new and existing + * props if you only want to handle changes. + * + * Calling `Component#setState` generally does not trigger this method. + * + * This method will not stop working in React 17. + * + * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps + * prevents this from being invoked. + * + * @deprecated 16.3, use static getDerivedStateFromProps instead + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#updating-state-based-on-props + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path + */ + UNSAFE_componentWillReceiveProps?(nextProps: Readonly

, nextContext: any): void; + /** + * Called immediately before rendering when new props or state is received. Not called for the initial render. + * + * Note: You cannot call `Component#setState` here. + * + * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps + * prevents this from being invoked. + * + * @deprecated 16.3, use getSnapshotBeforeUpdate instead; will stop working in React 17 + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#reading-dom-properties-before-an-update + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path + */ + componentWillUpdate?(nextProps: Readonly

, nextState: Readonly, nextContext: any): void; + /** + * Called immediately before rendering when new props or state is received. Not called for the initial render. + * + * Note: You cannot call `Component#setState` here. + * + * This method will not stop working in React 17. + * + * Note: the presence of getSnapshotBeforeUpdate or getDerivedStateFromProps + * prevents this from being invoked. + * + * @deprecated 16.3, use getSnapshotBeforeUpdate instead + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#reading-dom-properties-before-an-update + * @see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path + */ + UNSAFE_componentWillUpdate?(nextProps: Readonly

, nextState: Readonly, nextContext: any): void; + } + + interface Mixin extends ComponentLifecycle { + mixins?: Array>; + statics?: { + [key: string]: any; + }; + + displayName?: string; + propTypes?: ValidationMap; + contextTypes?: ValidationMap; + childContextTypes?: ValidationMap; + + getDefaultProps?(): P; + getInitialState?(): S; + } + + interface ComponentSpec extends Mixin { + render(): ReactNode; + + [propertyName: string]: any; + } + + function createRef(): RefObject; + + // will show `ForwardRef(${Component.displayName || Component.name})` in devtools by default, + // but can be given its own specific name + interface ForwardRefExoticComponent

extends NamedExoticComponent

{ + defaultProps?: Partial

; + propTypes?: WeakValidationMap

; + } + + function forwardRef(render: ForwardRefRenderFunction): ForwardRefExoticComponent & RefAttributes>; + + /** Ensures that the props do not include ref at all */ + type PropsWithoutRef

= + // Just Pick would be sufficient for this, but I'm trying to avoid unnecessary mapping over union types + // https://github.com/Microsoft/TypeScript/issues/28339 + 'ref' extends keyof P + ? Pick> + : P; + /** Ensures that the props do not include string ref, which cannot be forwarded */ + type PropsWithRef

= + // Just "P extends { ref?: infer R }" looks sufficient, but R will infer as {} if P is {}. + 'ref' extends keyof P + ? P extends { ref?: infer R } + ? string extends R + ? PropsWithoutRef

& { ref?: Exclude } + : P + : P + : P; + + type PropsWithChildren

= P & { children?: ReactNode }; + + /** + * NOTE: prefer ComponentPropsWithRef, if the ref is forwarded, + * or ComponentPropsWithoutRef when refs are not supported. + */ + type ComponentProps> = + T extends JSXElementConstructor + ? P + : T extends keyof JSX.IntrinsicElements + ? JSX.IntrinsicElements[T] + : {}; + type ComponentPropsWithRef = + T extends ComponentClass + ? PropsWithoutRef

& RefAttributes> + : PropsWithRef>; + type ComponentPropsWithoutRef = + PropsWithoutRef>; + + // will show `Memo(${Component.displayName || Component.name})` in devtools by default, + // but can be given its own specific name + type MemoExoticComponent> = NamedExoticComponent> & { + readonly type: T; + }; + + function memo

( + Component: SFC

, + propsAreEqual?: (prevProps: Readonly>, nextProps: Readonly>) => boolean + ): NamedExoticComponent

; + function memo>( + Component: T, + propsAreEqual?: (prevProps: Readonly>, nextProps: Readonly>) => boolean + ): MemoExoticComponent; + + type LazyExoticComponent> = ExoticComponent> & { + readonly _result: T; + }; + + function lazy>( + factory: () => Promise<{ default: T }> + ): LazyExoticComponent; + + // + // React Hooks + // ---------------------------------------------------------------------- + + // based on the code in https://github.com/facebook/react/pull/13968 + + // Unlike the class component setState, the updates are not allowed to be partial + type SetStateAction = S | ((prevState: S) => S); + // this technically does accept a second argument, but it's already under a deprecation warning + // and it's not even released so probably better to not define it. + type Dispatch = (value: A) => void; + // Since action _can_ be undefined, dispatch may be called without any parameters. + type DispatchWithoutAction = () => void; + // Unlike redux, the actions _can_ be anything + type Reducer = (prevState: S, action: A) => S; + // If useReducer accepts a reducer without action, dispatch may be called without any parameters. + type ReducerWithoutAction = (prevState: S) => S; + // types used to try and prevent the compiler from reducing S + // to a supertype common with the second argument to useReducer() + type ReducerState> = R extends Reducer ? S : never; + type ReducerAction> = R extends Reducer ? A : never; + // The identity check is done with the SameValue algorithm (Object.is), which is stricter than === + type ReducerStateWithoutAction> = + R extends ReducerWithoutAction ? S : never; + // TODO (TypeScript 3.0): ReadonlyArray + type DependencyList = ReadonlyArray; + + // NOTE: callbacks are _only_ allowed to return either void, or a destructor. + // The destructor is itself only allowed to return void. + type EffectCallback = () => (void | (() => void | undefined)); + + interface MutableRefObject { + current: T; + } + + // This will technically work if you give a Consumer or Provider but it's deprecated and warns + /** + * Accepts a context object (the value returned from `React.createContext`) and returns the current + * context value, as given by the nearest context provider for the given context. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usecontext + */ + function useContext(context: Context/*, (not public API) observedBits?: number|boolean */): T; + /** + * Returns a stateful value, and a function to update it. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usestate + */ + function useState(initialState: S | (() => S)): [S, Dispatch>]; + // convenience overload when first argument is ommitted + /** + * Returns a stateful value, and a function to update it. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usestate + */ + function useState(): [S | undefined, Dispatch>]; + /** + * An alternative to `useState`. + * + * `useReducer` is usually preferable to `useState` when you have complex state logic that involves + * multiple sub-values. It also lets you optimize performance for components that trigger deep + * updates because you can pass `dispatch` down instead of callbacks. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usereducer + */ + // overload where dispatch could accept 0 arguments. + function useReducer, I>( + reducer: R, + initializerArg: I, + initializer: (arg: I) => ReducerStateWithoutAction + ): [ReducerStateWithoutAction, DispatchWithoutAction]; + /** + * An alternative to `useState`. + * + * `useReducer` is usually preferable to `useState` when you have complex state logic that involves + * multiple sub-values. It also lets you optimize performance for components that trigger deep + * updates because you can pass `dispatch` down instead of callbacks. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usereducer + */ + // overload where dispatch could accept 0 arguments. + function useReducer>( + reducer: R, + initializerArg: ReducerStateWithoutAction, + initializer?: undefined + ): [ReducerStateWithoutAction, DispatchWithoutAction]; + /** + * An alternative to `useState`. + * + * `useReducer` is usually preferable to `useState` when you have complex state logic that involves + * multiple sub-values. It also lets you optimize performance for components that trigger deep + * updates because you can pass `dispatch` down instead of callbacks. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usereducer + */ + // overload where "I" may be a subset of ReducerState; used to provide autocompletion. + // If "I" matches ReducerState exactly then the last overload will allow initializer to be ommitted. + // the last overload effectively behaves as if the identity function (x => x) is the initializer. + function useReducer, I>( + reducer: R, + initializerArg: I & ReducerState, + initializer: (arg: I & ReducerState) => ReducerState + ): [ReducerState, Dispatch>]; + /** + * An alternative to `useState`. + * + * `useReducer` is usually preferable to `useState` when you have complex state logic that involves + * multiple sub-values. It also lets you optimize performance for components that trigger deep + * updates because you can pass `dispatch` down instead of callbacks. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usereducer + */ + // overload for free "I"; all goes as long as initializer converts it into "ReducerState". + function useReducer, I>( + reducer: R, + initializerArg: I, + initializer: (arg: I) => ReducerState + ): [ReducerState, Dispatch>]; + /** + * An alternative to `useState`. + * + * `useReducer` is usually preferable to `useState` when you have complex state logic that involves + * multiple sub-values. It also lets you optimize performance for components that trigger deep + * updates because you can pass `dispatch` down instead of callbacks. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usereducer + */ + + // I'm not sure if I keep this 2-ary or if I make it (2,3)-ary; it's currently (2,3)-ary. + // The Flow types do have an overload for 3-ary invocation with undefined initializer. + + // NOTE: without the ReducerState indirection, TypeScript would reduce S to be the most common + // supertype between the reducer's return type and the initialState (or the initializer's return type), + // which would prevent autocompletion from ever working. + + // TODO: double-check if this weird overload logic is necessary. It is possible it's either a bug + // in older versions, or a regression in newer versions of the typescript completion service. + function useReducer>( + reducer: R, + initialState: ReducerState, + initializer?: undefined + ): [ReducerState, Dispatch>]; + /** + * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument + * (`initialValue`). The returned object will persist for the full lifetime of the component. + * + * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable + * value around similar to how you’d use instance fields in classes. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#useref + */ + // TODO (TypeScript 3.0): + function useRef(initialValue: T): MutableRefObject; + // convenience overload for refs given as a ref prop as they typically start with a null value + /** + * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument + * (`initialValue`). The returned object will persist for the full lifetime of the component. + * + * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable + * value around similar to how you’d use instance fields in classes. + * + * Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type + * of the generic argument. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#useref + */ + // TODO (TypeScript 3.0): + function useRef(initialValue: T|null): RefObject; + // convenience overload for potentially undefined initialValue / call with 0 arguments + // has a default to stop it from defaulting to {} instead + /** + * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument + * (`initialValue`). The returned object will persist for the full lifetime of the component. + * + * Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable + * value around similar to how you’d use instance fields in classes. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#useref + */ + // TODO (TypeScript 3.0): + function useRef(): MutableRefObject; + /** + * The signature is identical to `useEffect`, but it fires synchronously after all DOM mutations. + * Use this to read layout from the DOM and synchronously re-render. Updates scheduled inside + * `useLayoutEffect` will be flushed synchronously, before the browser has a chance to paint. + * + * Prefer the standard `useEffect` when possible to avoid blocking visual updates. + * + * If you’re migrating code from a class component, `useLayoutEffect` fires in the same phase as + * `componentDidMount` and `componentDidUpdate`. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#uselayouteffect + */ + function useLayoutEffect(effect: EffectCallback, deps?: DependencyList): void; + /** + * Accepts a function that contains imperative, possibly effectful code. + * + * @param effect Imperative function that can return a cleanup function + * @param deps If present, effect will only activate if the values in the list change. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#useeffect + */ + function useEffect(effect: EffectCallback, deps?: DependencyList): void; + // NOTE: this does not accept strings, but this will have to be fixed by removing strings from type Ref + /** + * `useImperativeHandle` customizes the instance value that is exposed to parent components when using + * `ref`. As always, imperative code using refs should be avoided in most cases. + * + * `useImperativeHandle` should be used with `React.forwardRef`. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#useimperativehandle + */ + function useImperativeHandle(ref: Ref|undefined, init: () => R, deps?: DependencyList): void; + // I made 'inputs' required here and in useMemo as there's no point to memoizing without the memoization key + // useCallback(X) is identical to just using X, useMemo(() => Y) is identical to just using Y. + /** + * `useCallback` will return a memoized version of the callback that only changes if one of the `inputs` + * has changed. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usecallback + */ + // TODO (TypeScript 3.0): unknown> + function useCallback any>(callback: T, deps: DependencyList): T; + /** + * `useMemo` will only recompute the memoized value when one of the `deps` has changed. + * + * Usage note: if calling `useMemo` with a referentially stable function, also give it as the input in + * the second argument. + * + * ```ts + * function expensive () { ... } + * + * function Component () { + * const expensiveResult = useMemo(expensive, [expensive]) + * return ... + * } + * ``` + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usememo + */ + // allow undefined, but don't make it optional as that is very likely a mistake + function useMemo(factory: () => T, deps: DependencyList | undefined): T; + /** + * `useDebugValue` can be used to display a label for custom hooks in React DevTools. + * + * NOTE: We don’t recommend adding debug values to every custom hook. + * It’s most valuable for custom hooks that are part of shared libraries. + * + * @version 16.8.0 + * @see https://reactjs.org/docs/hooks-reference.html#usedebugvalue + */ + // the name of the custom hook is itself derived from the function name at runtime: + // it's just the function name without the "use" prefix. + function useDebugValue(value: T, format?: (value: T) => any): void; + + // + // Event System + // ---------------------------------------------------------------------- + // TODO: change any to unknown when moving to TS v3 + interface BaseSyntheticEvent { + nativeEvent: E; + currentTarget: C; + target: T; + bubbles: boolean; + cancelable: boolean; + defaultPrevented: boolean; + eventPhase: number; + isTrusted: boolean; + preventDefault(): void; + isDefaultPrevented(): boolean; + stopPropagation(): void; + isPropagationStopped(): boolean; + persist(): void; + timeStamp: number; + type: string; + } + + /** + * currentTarget - a reference to the element on which the event listener is registered. + * + * target - a reference to the element from which the event was originally dispatched. + * This might be a child element to the element on which the event listener is registered. + * If you thought this should be `EventTarget & T`, see https://github.com/DefinitelyTyped/DefinitelyTyped/pull/12239 + */ + interface SyntheticEvent extends BaseSyntheticEvent {} + + interface ClipboardEvent extends SyntheticEvent { + clipboardData: DataTransfer; + } + + interface CompositionEvent extends SyntheticEvent { + data: string; + } + + interface DragEvent extends MouseEvent { + dataTransfer: DataTransfer; + } + + interface PointerEvent extends MouseEvent { + pointerId: number; + pressure: number; + tangentialPressure: number; + tiltX: number; + tiltY: number; + twist: number; + width: number; + height: number; + pointerType: 'mouse' | 'pen' | 'touch'; + isPrimary: boolean; + } + + interface FocusEvent extends SyntheticEvent { + relatedTarget: EventTarget | null; + target: EventTarget & T; + } + + // tslint:disable-next-line:no-empty-interface + interface FormEvent extends SyntheticEvent { + } + + interface InvalidEvent extends SyntheticEvent { + target: EventTarget & T; + } + + interface ChangeEvent extends SyntheticEvent { + target: EventTarget & T; + } + + interface KeyboardEvent extends SyntheticEvent { + altKey: boolean; + charCode: number; + ctrlKey: boolean; + /** + * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method. + */ + getModifierState(key: string): boolean; + /** + * See the [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#named-key-attribute-values). for possible values + */ + key: string; + keyCode: number; + locale: string; + location: number; + metaKey: boolean; + repeat: boolean; + shiftKey: boolean; + which: number; + } + + interface MouseEvent extends UIEvent { + altKey: boolean; + button: number; + buttons: number; + clientX: number; + clientY: number; + ctrlKey: boolean; + /** + * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method. + */ + getModifierState(key: string): boolean; + metaKey: boolean; + movementX: number; + movementY: number; + pageX: number; + pageY: number; + relatedTarget: EventTarget | null; + screenX: number; + screenY: number; + shiftKey: boolean; + } + + interface TouchEvent extends SyntheticEvent { + altKey: boolean; + changedTouches: TouchList; + ctrlKey: boolean; + /** + * See [DOM Level 3 Events spec](https://www.w3.org/TR/uievents-key/#keys-modifier). for a list of valid (case-sensitive) arguments to this method. + */ + getModifierState(key: string): boolean; + metaKey: boolean; + shiftKey: boolean; + targetTouches: TouchList; + touches: TouchList; + } + + interface UIEvent extends SyntheticEvent { + detail: number; + view: AbstractView; + } + + interface WheelEvent extends MouseEvent { + deltaMode: number; + deltaX: number; + deltaY: number; + deltaZ: number; + } + + interface AnimationEvent extends SyntheticEvent { + animationName: string; + elapsedTime: number; + pseudoElement: string; + } + + interface TransitionEvent extends SyntheticEvent { + elapsedTime: number; + propertyName: string; + pseudoElement: string; + } + + // + // Event Handler Types + // ---------------------------------------------------------------------- + + type EventHandler> = { bivarianceHack(event: E): void }["bivarianceHack"]; + + type ReactEventHandler = EventHandler>; + + type ClipboardEventHandler = EventHandler>; + type CompositionEventHandler = EventHandler>; + type DragEventHandler = EventHandler>; + type FocusEventHandler = EventHandler>; + type FormEventHandler = EventHandler>; + type ChangeEventHandler = EventHandler>; + type KeyboardEventHandler = EventHandler>; + type MouseEventHandler = EventHandler>; + type TouchEventHandler = EventHandler>; + type PointerEventHandler = EventHandler>; + type UIEventHandler = EventHandler>; + type WheelEventHandler = EventHandler>; + type AnimationEventHandler = EventHandler>; + type TransitionEventHandler = EventHandler>; + + // + // Props / DOM Attributes + // ---------------------------------------------------------------------- + + /** + * @deprecated. This was used to allow clients to pass `ref` and `key` + * to `createElement`, which is no longer necessary due to intersection + * types. If you need to declare a props object before passing it to + * `createElement` or a factory, use `ClassAttributes`: + * + * ```ts + * var b: Button | null; + * var props: ButtonProps & ClassAttributes