Skip to content

Commit

Permalink
[tslint] blueprint-html-components fixer! (#3162)
Browse files Browse the repository at this point in the history
* enable strict mode in tslint-config

* sort imports before adding

* blueprint-html-components fixes imports

* refactors

* replaceTagName util

* test formatting

* add test for all imports

* h2/h5
  • Loading branch information
giladgray authored Nov 16, 2018
1 parent 1a724f5 commit 6fc03f3
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function getAllMatches(className: string) {
let currentMatch: RegExpMatchArray | null;
// tslint:disable-next-line:no-conditional-assignment
while ((currentMatch = BLUEPRINT_CLASSNAME_PATTERN.exec(className)) != null) {
ptMatches.push({ match: currentMatch[1], index: currentMatch.index });
ptMatches.push({ match: currentMatch[1], index: currentMatch.index || 0 });
}
return ptMatches;
}
Expand Down
36 changes: 33 additions & 3 deletions packages/tslint-config/src/rules/blueprintHtmlComponentsRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import * as Lint from "tslint";
import * as ts from "typescript";
import { addImportToFile } from "./utils/addImportToFile";
import { replaceTagName } from "./utils/replaceTagName";

const PATTERN = /^(h[1-6]|code|pre|blockquote|table)$/;

Expand All @@ -31,14 +33,42 @@ export class Rule extends Lint.Rules.AbstractRule {
}

function walk(ctx: Lint.WalkContext<void>): void {
return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void {
const tagFailures: Array<{
jsxTag: ts.JsxTagNameExpression;
newTagName: string;
replacements: Lint.Replacement[];
}> = [];

// walk file and build up array of failures
ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void {
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
const match = PATTERN.exec(node.tagName.getFullText());
if (match != null) {
const name = match[1].charAt(0).toUpperCase() + match[1].slice(1);
ctx.addFailureAt(node.tagName.getFullStart(), node.tagName.getFullWidth(), Rule.getFailure(name));
const newTagName = match[1].charAt(0).toUpperCase() + match[1].slice(1);
const replacements = [replaceTagName(node.tagName, newTagName)];

if (ts.isJsxOpeningElement(node)) {
// find closing tag after this opening tag to replace both in one failure
const [closingNode] = node.parent!.getChildren().filter(ts.isJsxClosingElement);
replacements.push(replaceTagName(closingNode.tagName, newTagName));
}

tagFailures.push({ jsxTag: node.tagName, newTagName, replacements });
}
}
return ts.forEachChild(node, cb);
});

if (tagFailures.length === 0) {
return;
}

// collect all potential new imports into one replacement (in first failure), after processing entire file.
const importsToAdd = addImportToFile(ctx.sourceFile, tagFailures.map(m => m.newTagName), "@blueprintjs/core");
tagFailures[0].replacements.push(importsToAdd);

// add all failures at the end
tagFailures.forEach(({ jsxTag, newTagName, replacements }) =>
ctx.addFailureAt(jsxTag.getFullStart(), jsxTag.getFullWidth(), Rule.getFailure(newTagName), replacements),
);
}
10 changes: 7 additions & 3 deletions packages/tslint-config/src/rules/blueprintIconComponentsRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,19 @@ function walk(ctx: Lint.WalkContext<string>): void {
if (ts.isJsxAttribute(node) && node.name.text === "icon") {
const { initializer } = node;
const option = ctx.options;
if (ts.isStringLiteral(initializer) && option === OPTION_COMPONENT) {
if (initializer === undefined) {
// no-op
} else if (ts.isStringLiteral(initializer) && option === OPTION_COMPONENT) {
// "tick" -> <TickIcon />
const iconName = `<${pascalCase(initializer.text)}Icon />`;
addFailure(ctx, node, Rule.componentMessage(iconName), `{${iconName}}`);
} else if (ts.isJsxExpression(initializer) && option === OPTION_LITERAL) {
// <TickIcon /> -> "tick"
const match = /<(\w+)Icon /.exec(initializer.getText());
const literal = match == null ? undefined : `"${dashCase(match[1])}"`;
addFailure(ctx, node, Rule.literalMessage(literal), literal);
if (match != null) {
const message = Rule.literalMessage(`"${dashCase(match[1])}"`);
addFailure(ctx, node, message, message);
}
}
}
return ts.forEachChild(node, cb);
Expand Down
6 changes: 2 additions & 4 deletions packages/tslint-config/src/rules/utils/addImportToFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ export function addImportToFile(file: ts.SourceFile, imports: string[], packageN
) {
const existingImports = packageToModify.importClause.namedBindings.elements.map(el => el.name.getText());
// Poor man's lodash.uniq without the dep.
const newImports = Array.from(new Set(existingImports.concat(imports))).sort((a, b) =>
a.toLowerCase().localeCompare(b.toLowerCase()),
);
const newImports = Array.from(new Set(existingImports.concat(imports))).sort();
const importString = `{ ${newImports.join(", ")} }`;
return Replacement.replaceNode(packageToModify.importClause.namedBindings, importString);
} else {
Expand All @@ -34,7 +32,7 @@ export function addImportToFile(file: ts.SourceFile, imports: string[], packageN
return compare(imp.moduleSpecifier.getText().slice(1, -1), packageName) === 1;
});
const startIndex = newImportIndex === -1 ? 0 : allImports[newImportIndex].getStart();
return Replacement.appendText(startIndex, `import { ${imports.join(", ")} } from "${packageName}";\n`);
return Replacement.appendText(startIndex, `import { ${imports.sort().join(", ")} } from "${packageName}";\n`);
}
}

Expand Down
13 changes: 13 additions & 0 deletions packages/tslint-config/src/rules/utils/replaceTagName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright 2018 Palantir Technologies, Inc. All rights reserved.
*
* Licensed under the terms of the LICENSE file distributed with this project.
*/

import { Replacement } from "tslint";
import { JsxTagNameExpression } from "typescript";

/** Replace the name of a JSX tag. */
export function replaceTagName(tagName: JsxTagNameExpression, newTagName: string) {
return new Replacement(tagName.getFullStart(), tagName.getFullWidth(), newTagName);
}
3 changes: 2 additions & 1 deletion packages/tslint-config/src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"compilerOptions": {
"lib": ["es6", "dom"],
"module": "commonjs",
"outDir": "../lib/rules"
"outDir": "../lib/rules",
"strict": true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Blockquote, Code, H2, H5, Pre } from "@blueprintjs/core";
<H5>Subtitle</H5>
<Pre>block</Pre>
<H2>Title</H2>
<Code>code element</Code>
<Blockquote />

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<h5>Subtitle</h5>
~~ [err % ("H5")]
<pre>block</pre>
~~~ [err % ("Pre")]
<h2>Title</h2>
~~ [err % ("H2")]
<code>code element</code>
~~~~ [err % ("Code")]
<blockquote />
~~~~~~~~~~ [err % ("Blockquote")]

[err]: use Blueprint <%s> component instead of JSX intrinsic element.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Blockquote, Code, H1, H3, Pre } from "@blueprintjs/core";
<H3>Subtitle</H3>
<Pre>block</Pre>

<H1>Title</H1>
<Code>code element</Code>
<Blockquote />
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { H3, Pre } from "@blueprintjs/core";
<H3>Subtitle</H3>
<Pre>block</Pre>

<h1>Title</h1>
~~ [err % ("H1")]

<code>code element</code>
~~~~ [err % ("Code")]

<blockquote />
~~~~~~~~~~ [err % ("Blockquote")]

<H3>Subtitle</H3>
<Pre>block</Pre>

[err]: use Blueprint <%s> component instead of JSX intrinsic element.

1 comment on commit 6fc03f3

@blueprint-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[tslint] blueprint-html-components fixer! (#3162)

Previews: documentation | landing | table

Please sign in to comment.