license import * as Lint from "tslint" ; import { isJsxElement , isJsxFragment , isJsxSelfClosingElement } from "tsutils/typeguard/3.0" ; import * as ts from "typescript" ; export class Rule extends Lint . Rules . AbstractRule { public static metadata : Lint . IRuleMetadata = { ruleName : "jsx-wrap-multiline" , description : "Checks that multiline JSX elements are wrapped in parens" , options : null , optionsDescription : "" , optionExamples : [ "true" ] , type : "style" , typescriptOnly : false , } ; public static FAILURE_NOT_WRAPPED = "Multiline JSX elements must be wrapped in parentheses" ; public static FAILURE_MISSING_NEW_LINE_AFTER_OPEN = "New line required after open parenthesis when wrapping multiline JSX elements" ; public static FAILURE_MISSING_NEW_LINE_BEFORE_CLOSE = "New line required before close parenthesis when wrapping multiline JSX elements" ; public apply ( sourceFile ) { return this . applyWithWalker ( new JsxWrapMultilineWalker ( sourceFile , this . ruleName , undefined ) ) ; } } class JsxWrapMultilineWalker extends Lint . AbstractWalker < void > { private scanner ? : ts . Scanner ; public walk ( sourceFile ) { const cb = ( node ) : void => { if ( isJsxElement ( node ) || isJsxSelfClosingElement ( node ) || isJsxFragment ( node ) ) { this . checkNode ( node , sourceFile ) ; } return ts . forEachChild ( node , cb ) ; } ; return ts . forEachChild ( sourceFile , cb ) ; } private checkNode ( node , sourceFile ) { const startLine = this . getLine ( node . getStart ( this . sourceFile ) ) ; const endLine = this . getLine ( node . getEnd ( ) ) ; if ( startLine === endLine ) { return ; } if ( node . parent == null ) { this . addFailureAtNode ( node , Rule . FAILURE_NOT_WRAPPED ) ; return ; } if ( isJsxElement ( node . parent ) || isJsxFragment ( node . parent ) ) { return ; } const scanner = this . getScanner ( sourceFile ) ; scanner . setTextPos ( node . getFullStart ( ) - 1 ) ; const siblings = node . parent . getChildren ( sourceFile ) ; const index = siblings . findIndex ( ( n ) => n . pos === node . pos && n . end === node . end ) ; const previousToken = siblings [ index - 1 ] ; const nextToken = siblings [ index + 1 ] ; if ( nextToken == null || nextToken . kind !== ts . SyntaxKind . CloseParenToken ) { this . addFailureAtNode ( node , Rule . FAILURE_NOT_WRAPPED ) ; return ; } const startParenLine = this . getLine ( previousToken . getStart ( sourceFile ) ) ; if ( startParenLine === startLine ) { this . addFailureAtNode ( previousToken , Rule . FAILURE_MISSING_NEW_LINE_AFTER_OPEN ) ; } const endParenLine = this . getLine ( nextToken . getStart ( sourceFile ) ) ; if ( endParenLine === endLine ) { this . addFailureAtNode ( nextToken , Rule . FAILURE_MISSING_NEW_LINE_BEFORE_CLOSE ) ; } } private getScanner ( sourceFile ) { if ( this . scanner === undefined ) { this . scanner = ts . createScanner ( ts . ScriptTarget . ES5 , false , ts . LanguageVariant . Standard , sourceFile . text ) ; } return this . scanner ; } private getLine ( position ) { return this . getSourceFile ( ) . getLineAndCharacterOfPosition ( position ) . line ; } }