@@ -21,6 +21,7 @@ package cadence
2121import (
2222 "errors"
2323 "fmt"
24+ "path/filepath"
2425 "strings"
2526
2627 "github.com/onflow/flow-cli/internal/util"
@@ -75,16 +76,21 @@ func newLinter(state *flowkit.State) *linter {
7576
7677func (l * linter ) lintFile (
7778 filePath string ,
78- ) (
79- []analysis.Diagnostic ,
80- error ,
81- ) {
82- diagnostics := make ([]analysis.Diagnostic , 0 )
79+ ) (diagnostics []analysis.Diagnostic , err error ) {
80+ // Recover from panics in the Cadence checker
81+ defer func () {
82+ if r := recover (); r != nil {
83+ // Convert panic to error instead of crashing
84+ err = fmt .Errorf ("internal error: %v" , r )
85+ }
86+ }()
87+
88+ diagnostics = make ([]analysis.Diagnostic , 0 )
8389 location := common .StringLocation (filePath )
8490
85- code , err := l .state .ReadFile (filePath )
86- if err != nil {
87- return nil , err
91+ code , readErr := l .state .ReadFile (filePath )
92+ if readErr != nil {
93+ return nil , readErr
8894 }
8995 codeStr := string (code )
9096
@@ -151,13 +157,132 @@ func (l *linter) lintFile(
151157 return diagnostics , nil
152158}
153159
160+ // isContractName returns true if the location string is a contract name (not a file path)
161+ func isContractName (locationString string ) bool {
162+ return ! strings .HasSuffix (locationString , ".cdc" )
163+ }
164+
165+ // resolveContractName attempts to resolve a location to a contract name
166+ func (l * linter ) resolveContractName (location common.StringLocation ) string {
167+ locationString := location .String ()
168+
169+ // If it's already a contract name, return it
170+ if isContractName (locationString ) {
171+ return locationString
172+ }
173+
174+ // Otherwise, try to find the contract by file path
175+ if l .state == nil {
176+ return ""
177+ }
178+
179+ contracts := l .state .Contracts ()
180+ if contracts == nil {
181+ return ""
182+ }
183+
184+ // Normalize the location path
185+ absLocation , err := filepath .Abs (locationString )
186+ if err != nil {
187+ absLocation = locationString
188+ }
189+
190+ // Search for matching contract
191+ for _ , contract := range * contracts {
192+ contractPath := contract .Location
193+ absContractPath , err := filepath .Abs (contractPath )
194+ if err != nil {
195+ absContractPath = contractPath
196+ }
197+
198+ if absLocation == absContractPath {
199+ return contract .Name
200+ }
201+ }
202+
203+ return ""
204+ }
205+
206+ // checkAccountAccess determines if checker and member locations are on the same account
207+ func (l * linter ) checkAccountAccess (checker * sema.Checker , memberLocation common.Location ) bool {
208+ // If both are AddressLocation, directly compare addresses
209+ if checkerAddr , ok := checker .Location .(common.AddressLocation ); ok {
210+ if memberAddr , ok := memberLocation .(common.AddressLocation ); ok {
211+ return checkerAddr .Address == memberAddr .Address
212+ }
213+ }
214+
215+ // For StringLocations, resolve to contract names and check deployments
216+ checkerLocation , ok := checker .Location .(common.StringLocation )
217+ if ! ok {
218+ return false
219+ }
220+
221+ memberStringLocation , ok := memberLocation .(common.StringLocation )
222+ if ! ok {
223+ return false
224+ }
225+
226+ checkerContractName := l .resolveContractName (checkerLocation )
227+ if checkerContractName == "" {
228+ return false
229+ }
230+
231+ memberContractName := l .resolveContractName (memberStringLocation )
232+ if memberContractName == "" {
233+ return false
234+ }
235+
236+ if l .state == nil {
237+ return false
238+ }
239+
240+ // Check across all networks if they're deployed to the same account
241+ networks := l .state .Networks ()
242+ if networks == nil {
243+ return false
244+ }
245+
246+ checkerContract , err := l .state .Contracts ().ByName (checkerContractName )
247+ if err != nil {
248+ return false
249+ }
250+
251+ memberContract , err := l .state .Contracts ().ByName (memberContractName )
252+ if err != nil {
253+ return false
254+ }
255+
256+ for _ , network := range * networks {
257+ checkerAddr , err := l .state .ContractAddress (checkerContract , network )
258+ if err != nil || checkerAddr == nil {
259+ continue
260+ }
261+
262+ memberAddr , err := l .state .ContractAddress (memberContract , network )
263+ if err != nil || memberAddr == nil {
264+ continue
265+ }
266+
267+ // If they're on the same account for this network, allow access
268+ if * checkerAddr == * memberAddr {
269+ return true
270+ }
271+ }
272+
273+ return false
274+ }
275+
154276// Create a new checker config with the given standard library
155277func (l * linter ) newCheckerConfig (standardLibrary * util.StandardLibrary ) * sema.Config {
156278 return & sema.Config {
157279 BaseValueActivationHandler : func (location common.Location ) * sema.VariableActivation {
158280 return standardLibrary .BaseValueActivation
159281 },
160- AccessCheckMode : sema .AccessCheckModeStrict ,
282+ MemberAccountAccessHandler : func (checker * sema.Checker , memberLocation common.Location ) bool {
283+ return l .checkAccountAccess (checker , memberLocation )
284+ },
285+ AccessCheckMode : sema .AccessCheckModeNotSpecifiedUnrestricted ,
161286 PositionInfoEnabled : true , // Must be enabled for linters
162287 ExtendedElaborationEnabled : true , // Must be enabled for linters
163288 ImportHandler : l .handleImport ,
0 commit comments