import { TSESTree } from "@typescript-eslint/experimental-utils" ; import { Reference , Scope , Variable , } from "@typescript-eslint/experimental-utils/dist/ts-eslint-scope" ; import { parseLoadArguments , isLoadFunction } from "../utils/load" ; import { findPropertiesRead } from "../utils/utils" ; import { isGetFunction } from "../utils/getFunction" ; export = { name : "load-object-before-read" , meta : { type : < "problem" | "suggestion" | "layout" > "problem" , messages : { loadBeforeRead : "An explicit load call on '{{name}}' for property '{{loadValue}}' needs to be made before the property can be read." , } , docs : { description : "Before you can read the properties of a proxy object, you must explicitly load the properties." , category : < "Best Practices" | "Stylistic Issues" | "Variables" | "Possible Errors" > "Possible Errors" , recommended : < false | "error" | "warn" > false , url : "https://docs.microsoft.com/office/dev/add-ins/develop/application-specific-api-model#load" , } , schema : [ ] , } , create : function ( context ) { function isInsideWriteStatement ( node ) { while ( node . parent ) { node = node . parent ; if ( node . type === TSESTree . AST_NODE_TYPES . AssignmentExpression ) return true ; } return false ; } function hasBeenLoaded ( node , loadLocation , propertyName ) { return ( loadLocation . has ( propertyName ) && node . range [ 1 ] > ( loadLocation . get ( propertyName ) ?? 0 ) ) ; } function findLoadBeforeRead ( scope ) { scope . variables . forEach ( ( variable ) => { let loadLocation = new Map < string , number > ( ) ; let getFound = false ; variable . references . forEach ( ( reference ) => { const node = reference . identifier ; if ( node . parent ?. type === TSESTree . AST_NODE_TYPES . VariableDeclarator ) { getFound = false ; if ( node . parent . init && isGetFunction ( node . parent . init ) ) { getFound = true ; return ; } } if ( node . parent ?. type === TSESTree . AST_NODE_TYPES . AssignmentExpression ) { getFound = false ; if ( isGetFunction ( node . parent . right ) ) { getFound = true ; return ; } } if ( ! getFound ) { return ; } if ( node . parent ?. type === TSESTree . AST_NODE_TYPES . MemberExpression ) { if ( isLoadFunction ( node . parent ) ) { const propertyNames = parseLoadArguments ( node . parent ) ; propertyNames . forEach ( ( propertyName ) => { loadLocation . set ( propertyName , node . range [ 1 ] ) ; } ) ; return ; } } const propertyName = findPropertiesRead ( node . parent ) ; if ( ! propertyName || hasBeenLoaded ( node , loadLocation , propertyName ) || hasBeenLoaded ( node , loadLocation , "*" ) || isInsideWriteStatement ( node ) ) { return ; } context . report ( { node : node , messageId : "loadBeforeRead" , data : { name : node . name , loadValue : propertyName } , } ) ; } ) ; } ) ; scope . childScopes . forEach ( findLoadBeforeRead ) ; } return { Program ( ) { findLoadBeforeRead ( context . getScope ( ) ) ; } , } ; } , } ;