lint

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 30, 2025 License: Apache-2.0 Imports: 22 Imported by: 1

README

Cadence-lint

The linter for Cadence. Find programming errors, bugs, stylistic errors and suspicious constructs.

How to Build

go build ./cmd/lint
Analyzing contracts of an account

To analyze all contracts of an account, specify the network and address. This requires you have the Flow CLI installed and configured (run flow init).

For example:

./lint -network mainnet -address 0x1654653399040a61
Analyzing a transaction

To analyze a transaction, specify the network and transaction ID. This requires you have the Flow CLI installed and configured (run flow init).

For example:

./lint -network mainnet -transaction 44fd8475eeded90d74e7594b10cf456b0866c78221e7f230fcfd4ba1155c542f
Only running some analyzers

By default, all available analyzers are run.

To list all available analyzers, run:

./lint -help

For example, to only run the reference-to-optional and the external-mutation analyzers, run:

./lint -network mainnet -address 0x1654653399040a61 \
    -analyze reference-to-optional \
    -analyze external-mutation
Analyzing contracts in a directory

To analyze all contracts in a directory, specify the path.

For example:

./lint -directory contracts

The files must be named with the .cdc extension and by their location ID of the program:

  • Contracts in accounts have the format A.<address>.<name>, e.g. A.e467b9dd11fa00df.FlowStorageFees, where
    • address: Address in hex format, e.g. e467b9dd11fa00df
    • name: The name of the contract, e.g FlowStorageFees
  • Transactions have the format t.<ID>, where
    • id: The ID of the transaction (its hash)
  • Scripts have the format s.<ID>, where
    • id: The ID of the script (its hash)
Analyzing contracts in a CSV file

To analyze all contracts in a CSV file, specify the path to the file.

For example:

./lint -csv contracts.csv

The CSV file must be in the following format:

  • Header: location,code
  • Columns:
    • location: The location ID of the program
      • Contracts in accounts have the format A.<address>.<name>, e.g. A.e467b9dd11fa00df.FlowStorageFees, where
        • address: Address in hex format, e.g. e467b9dd11fa00df
        • name: The name of the contract, e.g FlowStorageFees
      • Transactions have the format t.<ID>, where
        • id: The ID of the transaction (its hash)
      • Scripts have the format s.<ID>, where
        • id: The ID of the script (its hash)
    • code: The code of the contract, e.g. access(all) contract Test {}

Full example:

location,code
t.0000000000000000,"
import 0x1

transaction {
    prepare(signer: AuthAccount) {
        Test.hello()
    }
}
"
A.0000000000000001.Test,"
access(all) contract Test {

    access(all) fun hello() {
      log(""Hello, world!"")
    }
}
"

Documentation

Index

Constants

View Source
const (
	ReplacementCategory     = "replacement-hint"
	RemovalCategory         = "removal-hint"
	UnnecessaryCastCategory = "unnecessary-cast-hint"
	UnusedResultCategory    = "unused-result-hint"
	DeprecatedCategory      = "deprecated"
	CadenceV1Category       = "cadence-v1"
)

Variables

View Source
var Analyzers = map[string]*analysis.Analyzer{}
View Source
var CadenceV1Analyzer = (func() *analysis.Analyzer {
	return &analysis.Analyzer{
		Description: "Detects uses of removed features in Cadence 1.0",
		Requires: []*analysis.Analyzer{
			analysis.InspectorAnalyzer,
		},
		Run: func(pass *analysis.Pass) interface{} {
			analyzer := newCadenceV1Analyzer(pass)
			analyzer.AnalyzeAll()
			return nil
		},
	}
})()
View Source
var DeprecatedMemberAnalyzer = (func() *analysis.Analyzer {

	elementFilter := []ast.Element{
		(*ast.MemberExpression)(nil),
	}

	return &analysis.Analyzer{
		Description: "Detects uses of deprecated members",
		Requires: []*analysis.Analyzer{
			analysis.InspectorAnalyzer,
		},
		Run: func(pass *analysis.Pass) interface{} {
			inspector := pass.ResultOf[analysis.InspectorAnalyzer].(*ast.Inspector)

			program := pass.Program
			location := program.Location
			elaboration := program.Checker.Elaboration
			report := pass.Report

			inspector.Preorder(
				elementFilter,
				func(element ast.Element) {
					memberExpression, ok := element.(*ast.MemberExpression)
					if !ok {
						return
					}

					memberInfo, _ := elaboration.MemberExpressionMemberAccessInfo(memberExpression)
					member := memberInfo.Member
					if member == nil {
						return
					}

					docStringMatch := docStringDeprecationWarningPattern.FindStringSubmatch(member.DocString)
					if docStringMatch == nil {
						return
					}

					memberName := memberInfo.Member.Identifier.Identifier

					identifierRange := ast.NewRangeFromPositioned(nil, memberExpression.Identifier)

					report(
						analysis.Diagnostic{
							Location: location,
							Range:    identifierRange,
							Category: DeprecatedCategory,
							Message: fmt.Sprintf(
								"%s '%s' is deprecated",
								member.DeclarationKind.Name(),
								memberName,
							),
							SecondaryMessage: docStringMatch[1],
						},
					)
				},
			)

			return nil
		},
	}
})()
View Source
var NumberFunctionArgumentAnalyzer = (func() *analysis.Analyzer {

	elementFilter := []ast.Element{
		(*ast.IntegerExpression)(nil),
		(*ast.FixedPointExpression)(nil),
	}

	return &analysis.Analyzer{
		Description: "Detects redundant uses of number conversion functions.",
		Requires: []*analysis.Analyzer{
			analysis.InspectorAnalyzer,
		},
		Run: func(pass *analysis.Pass) interface{} {
			inspector := pass.ResultOf[analysis.InspectorAnalyzer].(*ast.Inspector)

			program := pass.Program
			location := program.Location
			elaboration := program.Checker.Elaboration
			report := pass.Report

			inspector.Preorder(
				elementFilter,
				func(element ast.Element) {

					var diagnostic *analysis.Diagnostic

					switch expr := element.(type) {
					case *ast.IntegerExpression:
						argumentData := elaboration.NumberConversionArgumentTypes(expr)
						if argumentData.Type == nil {
							return
						}
						diagnostic = suggestIntegerLiteralConversionReplacement(
							expr,
							location,
							argumentData.Type,
							argumentData.Range,
						)

					case *ast.FixedPointExpression:
						argumentData := elaboration.NumberConversionArgumentTypes(expr)
						if argumentData.Type == nil {
							return
						}
						diagnostic = suggestFixedPointLiteralConversionReplacement(
							expr,
							location,
							argumentData.Type,
							argumentData.Range,
						)

					default:
						return
					}

					if diagnostic != nil {
						report(*diagnostic)
					}
				},
			)

			return nil
		},
	}
})()
View Source
var RedundantCastAnalyzer = (func() *analysis.Analyzer {

	elementFilter := []ast.Element{
		(*ast.CastingExpression)(nil),
	}

	return &analysis.Analyzer{
		Description: "Detects unnecessary cast expressions",
		Requires: []*analysis.Analyzer{
			analysis.InspectorAnalyzer,
		},
		Run: func(pass *analysis.Pass) interface{} {
			inspector := pass.ResultOf[analysis.InspectorAnalyzer].(*ast.Inspector)

			program := pass.Program
			location := program.Location
			elaboration := program.Checker.Elaboration
			report := pass.Report

			inspector.Preorder(
				elementFilter,
				func(element ast.Element) {

					castingExpression, ok := element.(*ast.CastingExpression)
					if !ok {
						return
					}

					redundantType := elaboration.StaticCastTypes(castingExpression)
					if redundantType.ExprActualType != nil && isRedundantCast(
						castingExpression.Expression,
						redundantType.ExprActualType,
						redundantType.TargetType,
						redundantType.ExpectedType,
					) {
						report(
							analysis.Diagnostic{
								Location: location,
								Range:    ast.NewRangeFromPositioned(nil, castingExpression.TypeAnnotation),
								Category: UnnecessaryCastCategory,
								Message:  fmt.Sprintf("cast to `%s` is redundant", redundantType.TargetType),
							},
						)
						return
					}

					alwaysSucceedingTypes := elaboration.RuntimeCastTypes(castingExpression)
					if alwaysSucceedingTypes.Left != nil &&
						sema.IsSubType(alwaysSucceedingTypes.Left, alwaysSucceedingTypes.Right) {

						switch castingExpression.Operation {
						case ast.OperationFailableCast:
							report(
								analysis.Diagnostic{
									Location: location,
									Range:    ast.NewRangeFromPositioned(nil, castingExpression),
									Category: UnnecessaryCastCategory,
									Message: fmt.Sprintf("failable cast ('%s') from `%s` to `%s` always succeeds",
										ast.OperationFailableCast.Symbol(),
										alwaysSucceedingTypes.Left,
										alwaysSucceedingTypes.Right),
								},
							)

						case ast.OperationForceCast:
							report(
								analysis.Diagnostic{
									Location: location,
									Range:    ast.NewRangeFromPositioned(nil, castingExpression),
									Category: UnnecessaryCastCategory,
									Message: fmt.Sprintf("force cast ('%s') from `%s` to `%s` always succeeds",
										ast.OperationForceCast.Symbol(),
										alwaysSucceedingTypes.Left,
										alwaysSucceedingTypes.Right),
								},
							)

						default:
							panic(errors.NewUnreachableError())
						}
					}
				},
			)

			return nil
		},
	}
})()
View Source
var UnnecessaryForceAnalyzer = (func() *analysis.Analyzer {

	elementFilter := []ast.Element{
		(*ast.ForceExpression)(nil),
	}

	return &analysis.Analyzer{
		Description: "Detects unnecessary uses of the force operator",
		Requires: []*analysis.Analyzer{
			analysis.InspectorAnalyzer,
		},
		Run: func(pass *analysis.Pass) interface{} {
			inspector := pass.ResultOf[analysis.InspectorAnalyzer].(*ast.Inspector)

			program := pass.Program
			location := program.Location
			elaboration := program.Checker.Elaboration
			report := pass.Report

			inspector.Preorder(
				elementFilter,
				func(element ast.Element) {

					forceExpression, ok := element.(*ast.ForceExpression)
					if !ok {
						return
					}

					valueType := elaboration.ForceExpressionType(forceExpression)
					if valueType == nil {
						return
					}

					_, ok = valueType.(*sema.OptionalType)
					if !ok {
						report(
							analysis.Diagnostic{
								Location: location,
								Range:    ast.NewRangeFromPositioned(nil, element),
								Category: RemovalCategory,
								Message:  "unnecessary force operator",
							},
						)
					}
				},
			)

			return nil
		},
	}
})()
View Source
var UnusedResultAnalyzer = (func() *analysis.Analyzer {

	elementFilter := []ast.Element{
		(*ast.ExpressionStatement)(nil),
	}

	return &analysis.Analyzer{
		Description: "Detects unused results of expressions",
		Requires: []*analysis.Analyzer{
			analysis.InspectorAnalyzer,
		},
		Run: func(pass *analysis.Pass) interface{} {
			inspector := pass.ResultOf[analysis.InspectorAnalyzer].(*ast.Inspector)

			program := pass.Program
			location := program.Location
			elaboration := program.Checker.Elaboration
			report := pass.Report

			inspector.Preorder(
				elementFilter,
				func(element ast.Element) {

					expressionStatement, ok := element.(*ast.ExpressionStatement)
					if !ok {
						return
					}

					ty := elaboration.ExpressionTypes(expressionStatement.Expression).ActualType
					switch ty {
					case nil, sema.VoidType, sema.NeverType:

					default:
						report(
							analysis.Diagnostic{
								Location: location,
								Range:    ast.NewRangeFromPositioned(nil, element),
								Category: UnusedResultCategory,
								Message:  "unused result",
							},
						)
					}
				},
			)

			return nil
		},
	}
})()

Functions

func MemberIsDeprecated added in v0.9.0

func MemberIsDeprecated(docString string) bool

func RegisterAnalyzer added in v0.5.0

func RegisterAnalyzer(name string, analyzer *analysis.Analyzer)

func ReplacementHint

func ReplacementHint(
	expr ast.Expression,
	location common.Location,
	r ast.Range,
) *analysis.Diagnostic

Types

type CheckCastVisitor

type CheckCastVisitor struct {
	// contains filtered or unexported fields
}

func (*CheckCastVisitor) IsRedundantCast

func (d *CheckCastVisitor) IsRedundantCast(expr ast.Expression, exprInferredType, targetType sema.Type) bool

func (*CheckCastVisitor) VisitArrayExpression

func (d *CheckCastVisitor) VisitArrayExpression(expr *ast.ArrayExpression) bool

func (*CheckCastVisitor) VisitAttachExpression added in v0.5.0

func (d *CheckCastVisitor) VisitAttachExpression(_ *ast.AttachExpression) bool

func (*CheckCastVisitor) VisitBinaryExpression

func (d *CheckCastVisitor) VisitBinaryExpression(_ *ast.BinaryExpression) bool

func (*CheckCastVisitor) VisitBoolExpression

func (d *CheckCastVisitor) VisitBoolExpression(_ *ast.BoolExpression) bool

func (*CheckCastVisitor) VisitCastingExpression

func (d *CheckCastVisitor) VisitCastingExpression(_ *ast.CastingExpression) bool

func (*CheckCastVisitor) VisitConditionalExpression

func (d *CheckCastVisitor) VisitConditionalExpression(conditionalExpr *ast.ConditionalExpression) bool

func (*CheckCastVisitor) VisitCreateExpression

func (d *CheckCastVisitor) VisitCreateExpression(_ *ast.CreateExpression) bool

func (*CheckCastVisitor) VisitDestroyExpression

func (d *CheckCastVisitor) VisitDestroyExpression(_ *ast.DestroyExpression) bool

func (*CheckCastVisitor) VisitDictionaryExpression

func (d *CheckCastVisitor) VisitDictionaryExpression(expr *ast.DictionaryExpression) bool

func (*CheckCastVisitor) VisitFixedPointExpression

func (d *CheckCastVisitor) VisitFixedPointExpression(expr *ast.FixedPointExpression) bool

func (*CheckCastVisitor) VisitForceExpression

func (d *CheckCastVisitor) VisitForceExpression(_ *ast.ForceExpression) bool

func (*CheckCastVisitor) VisitFunctionExpression

func (d *CheckCastVisitor) VisitFunctionExpression(_ *ast.FunctionExpression) bool

func (*CheckCastVisitor) VisitIdentifierExpression

func (d *CheckCastVisitor) VisitIdentifierExpression(_ *ast.IdentifierExpression) bool

func (*CheckCastVisitor) VisitIndexExpression

func (d *CheckCastVisitor) VisitIndexExpression(_ *ast.IndexExpression) bool

func (*CheckCastVisitor) VisitIntegerExpression

func (d *CheckCastVisitor) VisitIntegerExpression(_ *ast.IntegerExpression) bool

func (*CheckCastVisitor) VisitInvocationExpression

func (d *CheckCastVisitor) VisitInvocationExpression(_ *ast.InvocationExpression) bool

func (*CheckCastVisitor) VisitMemberExpression

func (d *CheckCastVisitor) VisitMemberExpression(_ *ast.MemberExpression) bool

func (*CheckCastVisitor) VisitNilExpression

func (d *CheckCastVisitor) VisitNilExpression(_ *ast.NilExpression) bool

func (*CheckCastVisitor) VisitPathExpression

func (d *CheckCastVisitor) VisitPathExpression(_ *ast.PathExpression) bool

func (*CheckCastVisitor) VisitReferenceExpression

func (d *CheckCastVisitor) VisitReferenceExpression(_ *ast.ReferenceExpression) bool

func (*CheckCastVisitor) VisitStringExpression

func (d *CheckCastVisitor) VisitStringExpression(_ *ast.StringExpression) bool

func (*CheckCastVisitor) VisitStringTemplateExpression added in v1.2.0

func (d *CheckCastVisitor) VisitStringTemplateExpression(_ *ast.StringTemplateExpression) bool

func (*CheckCastVisitor) VisitUnaryExpression

func (d *CheckCastVisitor) VisitUnaryExpression(_ *ast.UnaryExpression) bool

func (*CheckCastVisitor) VisitVoidExpression

func (d *CheckCastVisitor) VisitVoidExpression(_ *ast.VoidExpression) bool

type Config

type Config struct {
	Analyzers  []*analysis.Analyzer
	Silent     bool
	UseColor   bool
	PrintError func(*Linter, error, common.Location)
}

type Linter

type Linter struct {
	Config Config

	Codes map[common.Location][]byte
	// contains filtered or unexported fields
}

func NewLinter

func NewLinter(config Config) *Linter

func (*Linter) AnalyzeAccount

func (l *Linter) AnalyzeAccount(address string, networkName string)

func (*Linter) AnalyzeCSV

func (l *Linter) AnalyzeCSV(path string)

func (*Linter) AnalyzeDirectory

func (l *Linter) AnalyzeDirectory(directory string)

func (*Linter) AnalyzeTransaction

func (l *Linter) AnalyzeTransaction(transactionID flow.Identifier, networkName string)

func (*Linter) PrettyPrintError

func (l *Linter) PrettyPrintError(err error, location common.Location)

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL