Remove redundant attributes with empty values
This commit is contained in:
parent
d474e4a097
commit
7dcd7442e8
8 changed files with 597 additions and 43 deletions
|
|
@ -18,6 +18,9 @@
|
|||
"autoplay": [
|
||||
"media"
|
||||
],
|
||||
"capture": [
|
||||
"input"
|
||||
],
|
||||
"checked": [
|
||||
"input"
|
||||
],
|
||||
|
|
@ -27,6 +30,9 @@
|
|||
"default": [
|
||||
"track"
|
||||
],
|
||||
"defaultchecked": [
|
||||
""
|
||||
],
|
||||
"defer": [
|
||||
"script"
|
||||
],
|
||||
|
|
@ -47,6 +53,12 @@
|
|||
"button",
|
||||
"input"
|
||||
],
|
||||
"hidden": [
|
||||
""
|
||||
],
|
||||
"itemscope": [
|
||||
""
|
||||
],
|
||||
"loop": [
|
||||
"media"
|
||||
],
|
||||
|
|
@ -91,5 +103,11 @@
|
|||
],
|
||||
"selected": [
|
||||
"option"
|
||||
],
|
||||
"suppresscontenteditablewarning": [
|
||||
""
|
||||
],
|
||||
"suppresshydrationwarning": [
|
||||
""
|
||||
]
|
||||
}
|
||||
|
|
@ -6,14 +6,15 @@ const path = require('path');
|
|||
const fromCamelCase = camelCase => camelCase.split(/(?=^|[A-Z])/).map(w => w.toLowerCase());
|
||||
|
||||
const BOOLEAN_ATTRS_PATH = path.join(__dirname, '..', 'boolean_attrs.json');
|
||||
const REDUNDANT_IF_EMPTY_ATTRS_PATH = path.join(__dirname, '..', 'redundant_if_empty_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");
|
||||
return await fs.readFile(REACT_TYPINGS_FILE, 'utf8');
|
||||
} catch (err) {
|
||||
if (err.code !== "ENOENT") {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err;
|
||||
}
|
||||
const source = await request(REACT_TYPINGS_URL);
|
||||
|
|
@ -22,33 +23,48 @@ const fetchReactTypingsSource = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const attrInterfaceToTagName = {
|
||||
'anchor': 'a',
|
||||
};
|
||||
|
||||
const reactSpecificAttributes = [
|
||||
'defaultChecked', 'defaultValue', 'suppressContentEditableWarning', 'suppressHydrationWarning',
|
||||
];
|
||||
|
||||
const processReactTypeDeclarations = async (source) => {
|
||||
let booleanAttributes = new Map();
|
||||
const booleanAttributes = new Map();
|
||||
const redundantIfEmptyAttributes = new Map();
|
||||
|
||||
const unvisited = [source];
|
||||
while (unvisited.length) {
|
||||
const node = unvisited.shift();
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.InterfaceDeclaration:
|
||||
const name = node.name.escapedText;
|
||||
let matches;
|
||||
if ((matches = /^([A-Za-z]+)HTMLAttributes/.exec(name))) {
|
||||
const tagName = matches[1].toLowerCase();
|
||||
if (!['all', 'webview'].includes(tagName)) {
|
||||
node.members
|
||||
.filter(n => n.kind === ts.SyntaxKind.PropertySignature)
|
||||
.filter(n => n.type.kind === ts.SyntaxKind.BooleanKeyword)
|
||||
.map(n => n.name.escapedText)
|
||||
.forEach(attr => {
|
||||
attr = attr.toLowerCase();
|
||||
if (!booleanAttributes.has(attr)) {
|
||||
booleanAttributes.set(attr, []);
|
||||
}
|
||||
booleanAttributes.get(attr).push(tagName);
|
||||
});
|
||||
if (node.kind === ts.SyntaxKind.InterfaceDeclaration) {
|
||||
const name = node.name.escapedText;
|
||||
let matches;
|
||||
if ((matches = /^([A-Za-z]*)HTMLAttributes/.exec(name))) {
|
||||
const tagName = [matches[1].toLowerCase()].map(n => attrInterfaceToTagName[n] || n)[0];
|
||||
if (!['all', 'webview'].includes(tagName)) {
|
||||
for (const n of node.members.filter(n => n.kind === ts.SyntaxKind.PropertySignature)) {
|
||||
// TODO Is escapedText the API for getting name?
|
||||
const attr = n.name.escapedText.toLowerCase();
|
||||
const types = n.type.kind === ts.SyntaxKind.UnionType
|
||||
? n.type.types.map(t => t.kind)
|
||||
: [n.type.kind];
|
||||
// If types includes boolean and string, make it a boolean attr to prevent it from being removed if empty value.
|
||||
if (types.includes(ts.SyntaxKind.BooleanKeyword)) {
|
||||
if (!booleanAttributes.has(attr)) {
|
||||
booleanAttributes.set(attr, []);
|
||||
}
|
||||
booleanAttributes.get(attr).push(tagName);
|
||||
} else if (types.includes(ts.SyntaxKind.StringKeyword) || types.includes(ts.SyntaxKind.NumberKeyword)) {
|
||||
if (!redundantIfEmptyAttributes.has(attr)) {
|
||||
redundantIfEmptyAttributes.set(attr, []);
|
||||
}
|
||||
redundantIfEmptyAttributes.get(attr).push(tagName);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// forEachChild doesn't seem to work if return value is number (e.g. Array.prototype.push return value).
|
||||
node.forEachChild(c => void unvisited.push(c));
|
||||
|
|
@ -60,6 +76,11 @@ const processReactTypeDeclarations = async (source) => {
|
|||
null,
|
||||
2,
|
||||
));
|
||||
await fs.writeFile(REDUNDANT_IF_EMPTY_ATTRS_PATH, JSON.stringify(
|
||||
Object.fromEntries([...redundantIfEmptyAttributes.entries()].sort((a, b) => a[0].localeCompare(b[0]))),
|
||||
null,
|
||||
2,
|
||||
));
|
||||
};
|
||||
|
||||
(async () => {
|
||||
|
|
|
|||
467
gen/redundant_if_empty_attrs.json
Normal file
467
gen/redundant_if_empty_attrs.json
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
{
|
||||
"abbr": [
|
||||
"td",
|
||||
"th"
|
||||
],
|
||||
"about": [
|
||||
""
|
||||
],
|
||||
"accept": [
|
||||
"input"
|
||||
],
|
||||
"acceptcharset": [
|
||||
"form"
|
||||
],
|
||||
"accesskey": [
|
||||
""
|
||||
],
|
||||
"action": [
|
||||
"form"
|
||||
],
|
||||
"allow": [
|
||||
"iframe"
|
||||
],
|
||||
"alt": [
|
||||
"area",
|
||||
"img",
|
||||
"input"
|
||||
],
|
||||
"as": [
|
||||
"link"
|
||||
],
|
||||
"autocapitalize": [
|
||||
""
|
||||
],
|
||||
"autocomplete": [
|
||||
"form",
|
||||
"input",
|
||||
"select",
|
||||
"textarea"
|
||||
],
|
||||
"autocorrect": [
|
||||
""
|
||||
],
|
||||
"autosave": [
|
||||
""
|
||||
],
|
||||
"cellpadding": [
|
||||
"table"
|
||||
],
|
||||
"cellspacing": [
|
||||
"table"
|
||||
],
|
||||
"challenge": [
|
||||
"keygen"
|
||||
],
|
||||
"charset": [
|
||||
"meta",
|
||||
"script"
|
||||
],
|
||||
"cite": [
|
||||
"blockquote",
|
||||
"del",
|
||||
"ins",
|
||||
"quote"
|
||||
],
|
||||
"classid": [
|
||||
"object"
|
||||
],
|
||||
"classname": [
|
||||
""
|
||||
],
|
||||
"color": [
|
||||
""
|
||||
],
|
||||
"cols": [
|
||||
"textarea"
|
||||
],
|
||||
"colspan": [
|
||||
"td",
|
||||
"th"
|
||||
],
|
||||
"content": [
|
||||
"meta"
|
||||
],
|
||||
"contextmenu": [
|
||||
""
|
||||
],
|
||||
"controlslist": [
|
||||
"media"
|
||||
],
|
||||
"coords": [
|
||||
"area"
|
||||
],
|
||||
"crossorigin": [
|
||||
"input",
|
||||
"link",
|
||||
"media",
|
||||
"script"
|
||||
],
|
||||
"data": [
|
||||
"object"
|
||||
],
|
||||
"datatype": [
|
||||
""
|
||||
],
|
||||
"datetime": [
|
||||
"del",
|
||||
"ins",
|
||||
"time"
|
||||
],
|
||||
"defaultvalue": [
|
||||
""
|
||||
],
|
||||
"dir": [
|
||||
""
|
||||
],
|
||||
"dirname": [
|
||||
"textarea"
|
||||
],
|
||||
"enctype": [
|
||||
"form"
|
||||
],
|
||||
"form": [
|
||||
"button",
|
||||
"fieldset",
|
||||
"input",
|
||||
"keygen",
|
||||
"label",
|
||||
"meter",
|
||||
"object",
|
||||
"output",
|
||||
"select",
|
||||
"textarea"
|
||||
],
|
||||
"formaction": [
|
||||
"button",
|
||||
"input"
|
||||
],
|
||||
"formenctype": [
|
||||
"button",
|
||||
"input"
|
||||
],
|
||||
"formmethod": [
|
||||
"button",
|
||||
"input"
|
||||
],
|
||||
"formtarget": [
|
||||
"button",
|
||||
"input"
|
||||
],
|
||||
"frameborder": [
|
||||
"iframe"
|
||||
],
|
||||
"headers": [
|
||||
"td",
|
||||
"th"
|
||||
],
|
||||
"height": [
|
||||
"canvas",
|
||||
"embed",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"object",
|
||||
"video"
|
||||
],
|
||||
"high": [
|
||||
"meter"
|
||||
],
|
||||
"href": [
|
||||
"a",
|
||||
"area",
|
||||
"base",
|
||||
"link"
|
||||
],
|
||||
"hreflang": [
|
||||
"a",
|
||||
"area",
|
||||
"link"
|
||||
],
|
||||
"htmlfor": [
|
||||
"label",
|
||||
"output"
|
||||
],
|
||||
"httpequiv": [
|
||||
"meta"
|
||||
],
|
||||
"id": [
|
||||
""
|
||||
],
|
||||
"integrity": [
|
||||
"link",
|
||||
"script"
|
||||
],
|
||||
"is": [
|
||||
""
|
||||
],
|
||||
"itemid": [
|
||||
""
|
||||
],
|
||||
"itemprop": [
|
||||
""
|
||||
],
|
||||
"itemref": [
|
||||
""
|
||||
],
|
||||
"itemtype": [
|
||||
""
|
||||
],
|
||||
"keyparams": [
|
||||
"keygen"
|
||||
],
|
||||
"keytype": [
|
||||
"keygen"
|
||||
],
|
||||
"kind": [
|
||||
"track"
|
||||
],
|
||||
"label": [
|
||||
"optgroup",
|
||||
"option",
|
||||
"track"
|
||||
],
|
||||
"lang": [
|
||||
""
|
||||
],
|
||||
"list": [
|
||||
"input"
|
||||
],
|
||||
"low": [
|
||||
"meter"
|
||||
],
|
||||
"manifest": [
|
||||
"html"
|
||||
],
|
||||
"marginheight": [
|
||||
"iframe"
|
||||
],
|
||||
"marginwidth": [
|
||||
"iframe"
|
||||
],
|
||||
"max": [
|
||||
"input",
|
||||
"meter",
|
||||
"progress"
|
||||
],
|
||||
"maxlength": [
|
||||
"input",
|
||||
"textarea"
|
||||
],
|
||||
"media": [
|
||||
"a",
|
||||
"area",
|
||||
"link",
|
||||
"source",
|
||||
"style"
|
||||
],
|
||||
"mediagroup": [
|
||||
"media"
|
||||
],
|
||||
"method": [
|
||||
"form"
|
||||
],
|
||||
"min": [
|
||||
"input",
|
||||
"meter"
|
||||
],
|
||||
"minlength": [
|
||||
"input",
|
||||
"textarea"
|
||||
],
|
||||
"name": [
|
||||
"button",
|
||||
"fieldset",
|
||||
"form",
|
||||
"iframe",
|
||||
"input",
|
||||
"keygen",
|
||||
"map",
|
||||
"meta",
|
||||
"object",
|
||||
"output",
|
||||
"param",
|
||||
"select",
|
||||
"textarea"
|
||||
],
|
||||
"nonce": [
|
||||
"script",
|
||||
"style"
|
||||
],
|
||||
"optimum": [
|
||||
"meter"
|
||||
],
|
||||
"pattern": [
|
||||
"input"
|
||||
],
|
||||
"ping": [
|
||||
"a"
|
||||
],
|
||||
"placeholder": [
|
||||
"",
|
||||
"input",
|
||||
"textarea"
|
||||
],
|
||||
"poster": [
|
||||
"video"
|
||||
],
|
||||
"prefix": [
|
||||
""
|
||||
],
|
||||
"preload": [
|
||||
"media"
|
||||
],
|
||||
"property": [
|
||||
""
|
||||
],
|
||||
"radiogroup": [
|
||||
""
|
||||
],
|
||||
"referrerpolicy": [
|
||||
"a",
|
||||
"iframe"
|
||||
],
|
||||
"rel": [
|
||||
"a",
|
||||
"area",
|
||||
"link"
|
||||
],
|
||||
"resource": [
|
||||
""
|
||||
],
|
||||
"results": [
|
||||
""
|
||||
],
|
||||
"role": [
|
||||
""
|
||||
],
|
||||
"rows": [
|
||||
"textarea"
|
||||
],
|
||||
"rowspan": [
|
||||
"td",
|
||||
"th"
|
||||
],
|
||||
"sandbox": [
|
||||
"iframe"
|
||||
],
|
||||
"scope": [
|
||||
"td",
|
||||
"th"
|
||||
],
|
||||
"scrolling": [
|
||||
"iframe"
|
||||
],
|
||||
"security": [
|
||||
""
|
||||
],
|
||||
"shape": [
|
||||
"area"
|
||||
],
|
||||
"size": [
|
||||
"input",
|
||||
"select"
|
||||
],
|
||||
"sizes": [
|
||||
"img",
|
||||
"link",
|
||||
"source"
|
||||
],
|
||||
"slot": [
|
||||
""
|
||||
],
|
||||
"span": [
|
||||
"col",
|
||||
"colgroup"
|
||||
],
|
||||
"src": [
|
||||
"embed",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"media",
|
||||
"script",
|
||||
"source",
|
||||
"track"
|
||||
],
|
||||
"srcdoc": [
|
||||
"iframe"
|
||||
],
|
||||
"srclang": [
|
||||
"track"
|
||||
],
|
||||
"srcset": [
|
||||
"img",
|
||||
"source"
|
||||
],
|
||||
"start": [
|
||||
"ol"
|
||||
],
|
||||
"step": [
|
||||
"input"
|
||||
],
|
||||
"summary": [
|
||||
"table"
|
||||
],
|
||||
"tabindex": [
|
||||
""
|
||||
],
|
||||
"target": [
|
||||
"a",
|
||||
"area",
|
||||
"base",
|
||||
"form"
|
||||
],
|
||||
"title": [
|
||||
""
|
||||
],
|
||||
"type": [
|
||||
"a",
|
||||
"embed",
|
||||
"input",
|
||||
"link",
|
||||
"menu",
|
||||
"object",
|
||||
"script",
|
||||
"source",
|
||||
"style"
|
||||
],
|
||||
"typeof": [
|
||||
""
|
||||
],
|
||||
"usemap": [
|
||||
"img",
|
||||
"object"
|
||||
],
|
||||
"value": [
|
||||
"button",
|
||||
"data",
|
||||
"input",
|
||||
"li",
|
||||
"meter",
|
||||
"option",
|
||||
"param",
|
||||
"progress",
|
||||
"select",
|
||||
"textarea"
|
||||
],
|
||||
"vocab": [
|
||||
""
|
||||
],
|
||||
"width": [
|
||||
"canvas",
|
||||
"col",
|
||||
"embed",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"object",
|
||||
"video"
|
||||
],
|
||||
"wmode": [
|
||||
"object"
|
||||
],
|
||||
"wrap": [
|
||||
"textarea"
|
||||
]
|
||||
}
|
||||
Reference in a new issue