diff --git a/README.md b/README.md index c7ab243..c61c781 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,9 @@ The built-in configuration preset you get with `"extends": "tslint-react"` is se - `jsx-no-multiline-js` - Disallows multiline JS expressions inside JSX blocks to promote readability - Rule options: _none_ +- `jsx-no-space-before-end-of-tag` + - Checks that JSX elements do not have a space before the '>' part. + - Rule options: _none_ - `jsx-no-string-ref` - Passing strings to the `ref` prop of React elements is considered a legacy feature and will soon be deprecated. Instead, [use a callback](https://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute). diff --git a/src/rules/jsxNoSpaceBeforeEndOfTagRule.ts b/src/rules/jsxNoSpaceBeforeEndOfTagRule.ts new file mode 100644 index 0000000..b490635 --- /dev/null +++ b/src/rules/jsxNoSpaceBeforeEndOfTagRule.ts @@ -0,0 +1,56 @@ +/** + * @license + * Copyright 2016 Palantir Technologies, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as Lint from "tslint"; +import { isJsxSelfClosingElement } from "tsutils"; +import * as ts from "typescript"; + +export class Rule extends Lint.Rules.AbstractRule { + /* tslint:disable:object-literal-sort-keys */ + public static metadata: Lint.IRuleMetadata = { + ruleName: "jsx-no-space-before-end-of-tag", + description: "Checks that JSX elements do not have a space before the '>' part.", + options: null, + optionsDescription: "", + optionExamples: ["true"], + type: "style", + typescriptOnly: false, + }; + /* tslint:enable:object-literal-sort-keys */ + + public static FAILURE_STRING = "JSX elements must not have a space before the '>' part"; + + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithFunction(sourceFile, walk); + } +} + +const closingLength = ">".length; +const isWhiteSpace = (char: string) => /\s/.test(char); +const hasWhitespaceBeforeClosing = (nodeText: string) => + isWhiteSpace(nodeText.charAt(nodeText.length - closingLength - 1)); + +function walk(ctx: Lint.WalkContext): void { + return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void { + if (isJsxSelfClosingElement(node)) { + if (hasWhitespaceBeforeClosing(node.getText(ctx.sourceFile))) { + ctx.addFailureAtNode(node, Rule.FAILURE_STRING); + } + } + return ts.forEachChild(node, cb); + }); +}