import * as ts from "typescript" ; import * as Lint from "tslint" ; import * as utils from "tsutils/typeguard/2.8" ; import * as Ignore from "./shared/ignore" ; import { createInvalidNode , CheckNodeResult , createCheckNodeRule } from "./shared/check-node" ; import { isFunctionLikeDeclaration , isVariableOrParameterOrPropertyDeclaration } from "./shared/typeguard" ; type Options = Ignore . IgnoreLocalOption & Ignore . IgnoreOption & Ignore . IgnoreRestParametersOption & Ignore . IgnoreReturnType ; export const Rule = createCheckNodeRule ( Ignore . checkNodeWithIgnore ( checkNode ) , "Only ReadonlyArray allowed." ) ; function checkNode ( node , ctx ) { const results = [ checkArrayType ( node , ctx ) , checkTypeReference ( node , ctx ) , checkImplicitType ( node , ctx ) , checkTupleType ( node , ctx ) ] ; return { invalidNodes : results . reduce ( ( merged , result ) => [ ... merged , ... result . invalidNodes ] , [ ] ) , skipChildren : results . some ( result => result . skipChildren === true ) } ; } function checkArrayType ( node , ctx ) { if ( utils . isArrayTypeNode ( node ) ) { if ( node . parent && utils . isTypeOperatorNode ( node . parent ) && node . parent . operator === ts . SyntaxKind . ReadonlyKeyword ) { return { invalidNodes : [ ] } ; } if ( node . parent && Ignore . shouldIgnore ( node . parent , ctx . options , ctx . sourceFile ) ) { return { invalidNodes : [ ] , skipChildren : true } ; } if ( ctx . options . ignoreRestParameters && node . parent && utils . isParameterDeclaration ( node . parent ) && node . parent . dotDotDotToken ) { return { invalidNodes : [ ] } ; } if ( ctx . options . ignoreReturnType && checkIsReturnTypeOrNestedWithIn ( node ) ) { return { invalidNodes : [ ] } ; } const [ major , minor ] = ts . version . split ( "." ) . map ( n => Number . parseInt ( n , 10 ) ) ; return { invalidNodes : [ createInvalidNode ( node , major > 3 || ( major === 3 && minor >= 4 ) ? getReadonlyKeywordFix ( node , ctx ) : getReadonlyArrayFix ( node , ctx ) ) ] } ; } return { invalidNodes : [ ] } ; } function checkTypeReference ( node , ctx ) { if ( utils . isTypeReferenceNode ( node ) && node . typeName . getText ( ctx . sourceFile ) === "Array" ) { if ( node . parent && Ignore . shouldIgnore ( node . parent , ctx . options , ctx . sourceFile ) ) { return { invalidNodes : [ ] , skipChildren : true } ; } if ( ctx . options . ignoreReturnType && checkIsReturnTypeOrNestedWithIn ( node ) ) { return { invalidNodes : [ ] } ; } return { invalidNodes : [ createInvalidNode ( node , [ new Lint . Replacement ( node . getStart ( ctx . sourceFile ) , 0 , "Readonly" ) ] ) ] } ; } return { invalidNodes : [ ] } ; } export function checkImplicitType ( node , ctx ) { if ( Ignore . shouldIgnore ( node , ctx . options , ctx . sourceFile ) ) { return { invalidNodes : [ ] , skipChildren : true } ; } if ( isVariableOrParameterOrPropertyDeclaration ( node ) && isUntypedAndHasArrayLiteralExpressionInitializer ( node ) ) { const length = node . name . getWidth ( ctx . sourceFile ) ; const nameText = node . name . getText ( ctx . sourceFile ) ; let typeArgument = "any" ; return { invalidNodes : [ createInvalidNode ( node . name , [ new Lint . Replacement ( node . name . end - length , length , `${ nameText }: ReadonlyArray<${ typeArgument }>` ) ] ) ] } ; } return { invalidNodes : [ ] } ; } function checkIsReturnTypeOrNestedWithIn ( node ) { return node . parent ? isFunctionLikeDeclaration ( node . parent ) && node === node . parent . type ? true : checkIsReturnTypeOrNestedWithIn ( node . parent ) : false ; } function isUntypedAndHasArrayLiteralExpressionInitializer ( node ) { return Boolean ( ! node . type && ( node . initializer && utils . isArrayLiteralExpression ( node . initializer ) ) ) ; } function checkTupleType ( node , ctx ) { const [ major , minor ] = ts . version . split ( "." ) . map ( n => Number . parseInt ( n , 10 ) ) ; if ( major > 3 || ( major === 3 && minor >= 4 ) ) { if ( utils . isTupleTypeNode ( node ) ) { if ( node . parent && utils . isTypeOperatorNode ( node . parent ) && node . parent . operator === ts . SyntaxKind . ReadonlyKeyword ) { return { invalidNodes : [ ] } ; } return { invalidNodes : [ createInvalidNode ( node , getReadonlyKeywordFix ( node , ctx ) ) ] } ; } } return { invalidNodes : [ ] } ; } function getReadonlyKeywordFix ( node , ctx ) { if ( utils . isArrayTypeNode ( node . parent ) ) { return [ new Lint . Replacement ( node . getStart ( ctx . sourceFile ) , 0 , "(readonly " ) , new Lint . Replacement ( node . end , 0 , ")" ) ] ; } return [ new Lint . Replacement ( node . getStart ( ctx . sourceFile ) , 0 , "readonly " ) ] ; } function getReadonlyArrayFix ( node , ctx ) { return [ new Lint . Replacement ( node . getStart ( ctx . sourceFile ) , 0 , "ReadonlyArray<" ) , new Lint . Replacement ( node . end - 2 , 2 , ">" ) ] ; }