import type { TSESLint , TSESTree } from '@typescript-eslint/experimental-utils' ; import { isArrowFunctionExpression , isFunctionDeclaration , isFunctionExpression , isIdentifier , isBlockStatement , } from '../utils/nodes' ; import { report , issueLocation , getMainFunctionTokenLocation , IssueLocation , toSecondaryLocation , } from '../utils/locations' ; import docsUrl from '../utils/docs-url' ; const message = 'This function expects {{expectedArguments}}, but {{providedArguments}} provided.' ; const rule = { meta : { messages : { tooManyArguments : message , sonarRuntime : '{{sonarRuntimeData}}' , } , type : 'problem' , docs : { description : 'Function calls should not pass extra arguments' , recommended : 'error' , url : docsUrl ( __filename ) , } , schema : [ { enum : [ 'sonar-runtime' ] , } , ] , } , create ( context ) { const callExpressionsToCheck = [ ] ; const usingArguments = new Set ( ) ; const emptyFunctions = new Set ( ) ; return { CallExpression ( node ) { const callExpr = node as TSESTree . CallExpression ; if ( isIdentifier ( callExpr . callee ) ) { const reference = context . getScope ( ) . references . find ( ref => ref . identifier === callExpr . callee ) ; const definition = reference && getSingleDefinition ( reference ) ; if ( definition ) { if ( definition . type === 'FunctionName' ) { checkFunction ( callExpr , definition . node ) ; } else if ( definition . type === 'Variable' ) { const { init } = definition . node ; if ( init && ( isFunctionExpression ( init ) || isArrowFunctionExpression ( init ) ) ) { checkFunction ( callExpr , init ) ; } } } } else if ( isArrowFunctionExpression ( callExpr . callee ) || isFunctionExpression ( callExpr . callee ) ) { checkFunction ( callExpr , callExpr . callee ) ; } } , ':function' ( node ) { const fn = node as TSESTree . FunctionExpression ; if ( isBlockStatement ( fn . body ) && fn . body . body . length === 0 && fn . params . length === 0 ) { emptyFunctions . add ( node ) ; } } , 'FunctionDeclaration > BlockStatement Identifier' ( node ) { checkArguments ( node as TSESTree . Identifier ) ; } , 'FunctionExpression > BlockStatement Identifier' ( node ) { checkArguments ( node as TSESTree . Identifier ) ; } , 'Program:exit' ( ) { callExpressionsToCheck . forEach ( ( { callExpr , functionNode } ) => { if ( ! usingArguments . has ( functionNode ) && ! emptyFunctions . has ( functionNode ) ) { reportIssue ( callExpr , functionNode ) ; } } ) ; } , } ; function getSingleDefinition ( reference ) { if ( reference && reference . resolved ) { const variable = reference . resolved ; if ( variable . defs . length === 1 ) { return variable . defs [ 0 ] ; } } return undefined ; } function checkArguments ( identifier ) { if ( identifier . name === 'arguments' ) { const reference = context . getScope ( ) . references . find ( ref => ref . identifier === identifier ) ; const definition = reference && getSingleDefinition ( reference ) ; if ( ! definition ) { const ancestors = context . getAncestors ( ) . reverse ( ) ; const fn = ancestors . find ( node => isFunctionDeclaration ( node ) || isFunctionExpression ( node ) ) ; if ( fn ) { usingArguments . add ( fn ) ; } } } } function checkFunction ( callExpr , functionNode ) { const hasRest = functionNode . params . some ( param => param . type === 'RestElement' ) ; if ( ! hasRest && callExpr . arguments . length > functionNode . params . length ) { callExpressionsToCheck . push ( { callExpr , functionNode } ) ; } } function reportIssue ( callExpr , functionNode ) { const paramLength = functionNode . params . length ; const argsLength = callExpr . arguments . length ; const expectedArguments = paramLength === 0 ? "no arguments" : paramLength === 1 ? "1 argument" : `${ paramLength } arguments` ; const providedArguments = argsLength === 0 ? "none was" : argsLength === 1 ? "1 was" : `${ argsLength } were` ; report ( context , { messageId : 'tooManyArguments' , data : { expectedArguments , providedArguments , } , node : callExpr . callee , } , getSecondaryLocations ( callExpr , functionNode ) , message ) ; } function getSecondaryLocations ( callExpr , functionNode ) { const paramLength = functionNode . params . length ; const secondaryLocations = [ ] ; if ( paramLength > 0 ) { const startLoc = functionNode . params [ 0 ] . loc ; const endLoc = functionNode . params [ paramLength - 1 ] . loc ; secondaryLocations . push ( issueLocation ( startLoc , endLoc , 'Formal parameters' ) ) ; } else { const fnToken = getMainFunctionTokenLocation ( functionNode , undefined , context ) ; if ( fnToken ) { secondaryLocations . push ( issueLocation ( fnToken , fnToken , 'Formal parameters' ) ) ; } } callExpr . arguments . forEach ( ( argument , index ) => { if ( index >= paramLength ) { secondaryLocations . push ( toSecondaryLocation ( argument , 'Extra argument' ) ) ; } } ) ; return secondaryLocations ; } } , } ; export = rule ;