diff --git a/.gitignore b/.gitignore index fdd1235b0..a40df6891 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ src/cmd/stubs/phpstorm-stubs/ y.output .idea vendor +src/tests/golden/testdata/quickfix/*.fix diff --git a/src/linter/block.go b/src/linter/block.go index ee1401ca6..397a9de84 100644 --- a/src/linter/block.go +++ b/src/linter/block.go @@ -378,6 +378,7 @@ func (b *blockWalker) EnterNode(n ir.Node) (res bool) { for _, st := range s.Stmts { b.addStatement(st) } + b.handleDeclareSection(s) case *ir.StmtList: for _, st := range s.Stmts { b.addStatement(st) @@ -551,6 +552,23 @@ func (b *blockWalker) handleAndCheckGlobalStmt(s *ir.GlobalStmt) { } } +func (b *blockWalker) handleDeclareSection(root *ir.Root) { + isExist := false + + for _, stmt := range root.Stmts { + _, ok := stmt.(*ir.DeclareStmt) + if ok { + isExist = true + break + } + } + + if !isExist { + b.report(root, LevelWarning, "noDeclareSection", "Missed declare(strict_types = 1) directive") + b.r.addQuickFix("noDeclareSection", b.linter.quickfix.CreateDeclareStrictTypes(root)) + } +} + func (b *blockWalker) handleFunction(fun *ir.FunctionStmt) bool { if b.ignoreFunctionBodies { return false @@ -823,10 +841,10 @@ func (b *blockWalker) handleTry(s *ir.TryStmt) bool { }) } - for nm, types := range varTypes { + for nm, typs := range varTypes { var flags meta.VarFlags flags.SetAlwaysDefined(defCounts[nm] == linksCount) - b.ctx.sc.AddVarName(nm, types, "all branches try catch", flags) + b.ctx.sc.AddVarName(nm, typs, "all branches try catch", flags) } if finallyCtx != nil { @@ -1909,10 +1927,10 @@ func (b *blockWalker) handleSwitch(s *ir.SwitchStmt) bool { }) } - for nm, types := range varTypes { + for nm, typs := range varTypes { var flags meta.VarFlags flags.SetAlwaysDefined(defCounts[nm] == linksCount) - b.ctx.sc.AddVarName(nm, types, "all cases", flags) + b.ctx.sc.AddVarName(nm, typs, "all cases", flags) } return false diff --git a/src/linter/quickfix.go b/src/linter/quickfix.go index ef83aa8ad..b7e463b4b 100644 --- a/src/linter/quickfix.go +++ b/src/linter/quickfix.go @@ -47,6 +47,14 @@ func (g *QuickFixGenerator) NullForNotNullableProperty(prop *ir.PropertyStmt) qu } } +func (g *QuickFixGenerator) CreateDeclareStrictTypes(root *ir.Root) quickfix.TextEdit { + return quickfix.TextEdit{ + StartPos: root.Position.StartPos, + EndPos: root.Position.StartPos, + Replacement: "declare(strict_types = 1);\n", + } +} + func (g *QuickFixGenerator) GetType(node ir.Node, isFunctionName, nodeText string, isNegative bool) quickfix.TextEdit { pos := ir.GetPosition(node) diff --git a/src/linter/report.go b/src/linter/report.go index d1914da36..553118234 100644 --- a/src/linter/report.go +++ b/src/linter/report.go @@ -25,6 +25,15 @@ func addBuiltinCheckers(reg *CheckersRegistry) { After: `$s = strip_tags($s, '
')`, }, + { + Name: "noDeclareSection", + Default: true, + Quickfix: true, + Comment: "Report declare(strict_types=1) has not been set.", + Before: ` `, + After: `declare(strict_types = 1);`, + }, + { Name: "emptyStmt", Default: true, @@ -1185,8 +1194,8 @@ func DiffReports(gitRepo string, diffArgs []string, changesList []git.Change, ch } } - old := reportListToMap(oldList) - new := reportListToMap(newList) + oldReportMap := reportListToMap(oldList) + newReportMap := reportListToMap(newList) changes := gitChangesToMap(changesList) var mu sync.Mutex @@ -1196,7 +1205,7 @@ func DiffReports(gitRepo string, diffArgs []string, changesList []git.Change, ch limitCh := make(chan struct{}, maxConcurrency) - for filename, list := range new { + for filename, list := range newReportMap { wg.Add(1) go func(filename string, list []*Report) { limitCh <- struct{}{} @@ -1212,7 +1221,7 @@ func DiffReports(gitRepo string, diffArgs []string, changesList []git.Change, ch oldName = filename // full diff mode } - reports, err := diffReportsList(gitRepo, ignoreCommits, diffArgs, filename, c, old[oldName], list) + reports, err := diffReportsList(gitRepo, ignoreCommits, diffArgs, filename, c, oldReportMap[oldName], list) if err != nil { mu.Lock() resErr = err @@ -1266,8 +1275,8 @@ func diffReportsList(gitRepo string, ignoreCommits map[string]struct{}, diffArgs } } - old, oldMaxLine := reportListToPerLineMap(oldList) - new, newMaxLine := reportListToPerLineMap(newList) + oldReportMap, oldMaxLine := reportListToPerLineMap(oldList) + newReportMap, newMaxLine := reportListToPerLineMap(newList) var maxLine = oldMaxLine if newMaxLine > maxLine { @@ -1284,17 +1293,17 @@ func diffReportsList(gitRepo string, ignoreCommits map[string]struct{}, diffArgs // just deletion if ok && ch.new.HaveRange && ch.new.Range == 0 { oldLine = ch.old.To - newLine-- // cancel the increment of newLine, because code was deleted, no new lines added + newLine-- // cancel the increment of newLine, because code was deleted, no newReportMap lines added continue } - res = maybeAppendReports(res, new, old, newLine, oldLine, blame, ignoreCommits) + res = maybeAppendReports(res, newReportMap, oldReportMap, newLine, oldLine, blame, ignoreCommits) if ok { oldLine = 0 // all changes and additions must be checked for j := newLine + 1; j <= ch.new.To; j++ { newLine = j - res = maybeAppendReports(res, new, old, newLine, oldLine, blame, ignoreCommits) + res = maybeAppendReports(res, newReportMap, oldReportMap, newLine, oldLine, blame, ignoreCommits) } oldLine = ch.old.To } diff --git a/src/tests/golden/testdata/quickfix/noDeclareSection.php b/src/tests/golden/testdata/quickfix/noDeclareSection.php new file mode 100644 index 000000000..8ecb506ce --- /dev/null +++ b/src/tests/golden/testdata/quickfix/noDeclareSection.php @@ -0,0 +1,4 @@ +