Repository: VKCOM/noverify
Pull request #578 (2020-07-14 10:14:16)

Solve "you are not allowed to disable linter" only once per file #569
solve #569

i582: > I would suggest to print the diagnostic at the phpdoc that tries to disable linter instead. It would be more relevant there and you don't need a map too.

If I understand correctly, do you suggest printing all places where there was an attempt to disable linter?

Patch:
diff --git a/src/cmd/git_main.go b/src/cmd/git_main.go
index c18d291..817e0cb 100644
--- a/src/cmd/git_main.go
+++ b/src/cmd/git_main.go
@@ -21,7 +21,7 @@ func gitParseUntracked(l *linterRunner) []*linter.Report {
 		log.Fatalf("get untracked files: %v", err)
 	}
 
-	return linter.ParseFilenames(linter.ReadFilenames(filenames, nil))
+	return linter.ParseFilenames(linter.ReadFilenames(filenames, nil), nil)
 }
 
 func parseIndexOnlyFiles(l *linterRunner) {
@@ -29,7 +29,7 @@ func parseIndexOnlyFiles(l *linterRunner) {
 		return
 	}
 	filenames := strings.Split(l.args.indexOnlyFiles, ",")
-	linter.ParseFilenames(linter.ReadFilenames(filenames, nil))
+	linter.ParseFilenames(linter.ReadFilenames(filenames, nil), nil)
 }
 
 // Not the best name, and not the best function signature.
@@ -59,14 +59,14 @@ func gitRepoComputeReportsFromCommits(l *linterRunner, logArgs, diffArgs []strin
 		}
 
 		start = time.Now()
-		linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, nil))
+		linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, nil), nil)
 		parseIndexOnlyFiles(l)
 		log.Printf("Indexed old commit in %s", time.Since(start))
 
 		meta.SetIndexingComplete(true)
 
 		start = time.Now()
-		oldReports = linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, linter.ExcludeRegex))
+		oldReports = linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, linter.ExcludeRegex), nil)
 		log.Printf("Parsed old commit for %s (%d reports)", time.Since(start), len(oldReports))
 
 		meta.ResetInfo()
@@ -75,34 +75,34 @@ func gitRepoComputeReportsFromCommits(l *linterRunner, logArgs, diffArgs []strin
 		}
 
 		start = time.Now()
-		linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitTo, nil))
+		linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitTo, nil), nil)
 		log.Printf("Indexed new commit in %s", time.Since(start))
 
 		meta.SetIndexingComplete(true)
 
 		start = time.Now()
-		reports = linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitTo, linter.ExcludeRegex))
+		reports = linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitTo, linter.ExcludeRegex), nil)
 		log.Printf("Parsed new commit in %s (%d reports)", time.Since(start), len(reports))
 	} else {
 		start = time.Now()
-		linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitTo, nil))
+		linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitTo, nil), nil)
 		parseIndexOnlyFiles(l)
 		log.Printf("Indexing complete in %s", time.Since(start))
 
 		meta.SetIndexingComplete(true)
 
 		start = time.Now()
-		oldReports = linter.ParseFilenames(linter.ReadOldFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, changes))
+		oldReports = linter.ParseFilenames(linter.ReadOldFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, changes), nil)
 		log.Printf("Parsed old files versions for %s", time.Since(start))
 
 		start = time.Now()
 		meta.SetIndexingComplete(false)
-		linter.ParseFilenames(linter.ReadFilesFromGitWithChanges(l.args.gitRepo, l.args.mutable.gitCommitTo, changes))
+		linter.ParseFilenames(linter.ReadFilesFromGitWithChanges(l.args.gitRepo, l.args.mutable.gitCommitTo, changes), nil)
 		meta.SetIndexingComplete(true)
 		log.Printf("Indexed files versions for %s", time.Since(start))
 
 		start = time.Now()
-		reports = linter.ParseFilenames(linter.ReadFilesFromGitWithChanges(l.args.gitRepo, l.args.mutable.gitCommitTo, changes))
+		reports = linter.ParseFilenames(linter.ReadFilesFromGitWithChanges(l.args.gitRepo, l.args.mutable.gitCommitTo, changes), nil)
 		log.Printf("Parsed new file versions in %s", time.Since(start))
 	}
 
@@ -129,25 +129,25 @@ func gitRepoComputeReportsFromLocalChanges(l *linterRunner) (oldReports, reports
 	log.Printf("You have changes in your work tree, showing diff between %s and work tree", l.args.mutable.gitCommitFrom)
 
 	start := time.Now()
-	linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, nil))
+	linter.ParseFilenames(linter.ReadFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, nil), nil)
 	parseIndexOnlyFiles(l)
 	log.Printf("Indexing complete in %s", time.Since(start))
 
 	meta.SetIndexingComplete(true)
 
 	start = time.Now()
-	oldReports = linter.ParseFilenames(linter.ReadOldFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, changes))
+	oldReports = linter.ParseFilenames(linter.ReadOldFilesFromGit(l.args.gitRepo, l.args.mutable.gitCommitFrom, changes), nil)
 	log.Printf("Parsed old files versions for %s", time.Since(start))
 
 	start = time.Now()
 	meta.SetIndexingComplete(false)
-	linter.ParseFilenames(linter.ReadChangesFromWorkTree(l.args.gitWorkTree, changes))
+	linter.ParseFilenames(linter.ReadChangesFromWorkTree(l.args.gitWorkTree, changes), nil)
 	gitParseUntracked(l)
 	meta.SetIndexingComplete(true)
 	log.Printf("Indexed new files versions for %s", time.Since(start))
 
 	start = time.Now()
-	reports = linter.ParseFilenames(linter.ReadChangesFromWorkTree(l.args.gitWorkTree, changes))
+	reports = linter.ParseFilenames(linter.ReadChangesFromWorkTree(l.args.gitWorkTree, changes), nil)
 	reports = append(reports, gitParseUntracked(l)...)
 	log.Printf("Parsed new file versions in %s", time.Since(start))
 
diff --git a/src/cmd/linter_runner.go b/src/cmd/linter_runner.go
index 79218de..2adb15f 100644
--- a/src/cmd/linter_runner.go
+++ b/src/cmd/linter_runner.go
@@ -7,10 +7,11 @@ import (
 	"regexp"
 	"strings"
 
+	"github.com/client9/misspell"
+
 	"github.com/VKCOM/noverify/src/baseline"
 	"github.com/VKCOM/noverify/src/linter"
 	"github.com/VKCOM/noverify/src/rules"
-	"github.com/client9/misspell"
 )
 
 type linterRunner struct {
diff --git a/src/cmd/main.go b/src/cmd/main.go
index 0b1ca2a..3466895 100644
--- a/src/cmd/main.go
+++ b/src/cmd/main.go
@@ -175,7 +175,7 @@ func mainNoExit(ruleSets []*rules.Set, args *cmdlineArguments, cfg *MainConfig)
 	linter.AnalysisFiles = flag.Args()
 
 	log.Printf("Indexing %+v", flag.Args())
-	linter.ParseFilenames(linter.ReadFilenames(flag.Args(), nil))
+	linter.ParseFilenames(linter.ReadFilenames(flag.Args(), nil), l.allowDisableRegex)
 	parseIndexOnlyFiles(&l)
 	meta.SetIndexingComplete(true)
 	log.Printf("Linting")
@@ -185,7 +185,7 @@ func mainNoExit(ruleSets []*rules.Set, args *cmdlineArguments, cfg *MainConfig)
 		filenames = strings.Split(args.fullAnalysisFiles, ",")
 	}
 
-	reports := linter.ParseFilenames(linter.ReadFilenames(filenames, linter.ExcludeRegex))
+	reports := linter.ParseFilenames(linter.ReadFilenames(filenames, linter.ExcludeRegex), l.allowDisableRegex)
 	if args.outputBaseline {
 		if err := createBaseline(&l, cfg, reports); err != nil {
 			return 1, fmt.Errorf("write baseline: %v", err)
@@ -239,7 +239,7 @@ func createBaseline(l *linterRunner, cfg *MainConfig, reports []*linter.Report)
 
 func analyzeReports(l *linterRunner, cfg *MainConfig, diff []*linter.Report) (criticalReports int) {
 	filtered := make([]*linter.Report, 0, len(diff))
-	var linterErrors []string
+
 	for _, r := range diff {
 		if cfg.BeforeReport != nil && !cfg.BeforeReport(r) {
 			continue
@@ -250,9 +250,7 @@ func analyzeReports(l *linterRunner, cfg *MainConfig, diff []*linter.Report) (cr
 
 		if r.IsDisabledByUser() {
 			filename := r.GetFilename()
-			if !canBeDisabled(l, filename) {
-				linterErrors = append(linterErrors, fmt.Sprintf("You are not allowed to disable linter for file '%s'", filename))
-			} else {
+			if canBeDisabled(l, filename) {
 				continue
 			}
 		}
@@ -271,7 +269,6 @@ func analyzeReports(l *linterRunner, cfg *MainConfig, diff []*linter.Report) (cr
 		}
 		list := &reportList{
 			Reports: filtered,
-			Errors:  linterErrors,
 		}
 		d := json.NewEncoder(l.outputFp)
 		if err := d.Encode(list); err != nil {
@@ -279,9 +276,6 @@ func analyzeReports(l *linterRunner, cfg *MainConfig, diff []*linter.Report) (cr
 			panic(fmt.Sprintf("report list marshaling failed: %v", err))
 		}
 	} else {
-		for _, err := range linterErrors {
-			fmt.Fprintf(l.outputFp, "%s\n", err)
-		}
 		for _, r := range filtered {
 			if isCritical(l, r) {
 				fmt.Fprintf(l.outputFp, "<critical> %s\n", r.String())
diff --git a/src/langsrv/langsrv.go b/src/langsrv/langsrv.go
index 8ebf190..94da0c7 100644
--- a/src/langsrv/langsrv.go
+++ b/src/langsrv/langsrv.go
@@ -169,7 +169,7 @@ func handleInitialize(req *baseRequest) error {
 	go func() {
 		linter.AnalysisFiles = []string{params.RootURI.Filename()}
 
-		linter.ParseFilenames(linter.ReadFilenames(linter.AnalysisFiles, linter.ExcludeRegex))
+		linter.ParseFilenames(linter.ReadFilenames(linter.AnalysisFiles, linter.ExcludeRegex), nil)
 
 		meta.SetIndexingComplete(true)
 
diff --git a/src/langsrv/state.go b/src/langsrv/state.go
index 085ac7d..ac9a663 100644
--- a/src/langsrv/state.go
+++ b/src/langsrv/state.go
@@ -40,7 +40,7 @@ func openFile(filename, contents string) {
 	}
 
 	// just parse file, do not fully analyze it as indexing is not yet done
-	rootNode, _, err := linter.ParseContents(filename, []byte(contents), nil)
+	rootNode, _, err := linter.ParseContents(filename, []byte(contents), nil, nil)
 	if err != nil {
 		log.Printf("Could not parse %s: %s", filename, err.Error())
 		lintdebug.Send("Could not parse %s: %s", filename, err.Error())
@@ -68,7 +68,7 @@ func changeFileNonLocked(filename, contents string) {
 	// parse file, update index for it, and then generate diagnostics based on new index
 	meta.SetIndexingComplete(false)
 
-	rootNode, w, err := linter.ParseContents(filename, []byte(contents), nil)
+	rootNode, w, err := linter.ParseContents(filename, []byte(contents), nil, nil)
 	if err != nil {
 		log.Printf("Could not parse %s: %s", filename, err.Error())
 		lintdebug.Send("Could not parse %s: %s", filename, err.Error())
diff --git a/src/linter/cache.go b/src/linter/cache.go
index af56869..0fb2afc 100644
--- a/src/linter/cache.go
+++ b/src/linter/cache.go
@@ -58,7 +58,7 @@ type fileMeta struct {
 // IndexFile parses the file and fills in the meta info. Can use cache.
 func IndexFile(filename string, contents []byte) error {
 	if CacheDir == "" {
-		_, w, err := ParseContents(filename, contents, nil)
+		_, w, err := ParseContents(filename, contents, nil, nil)
 		if w != nil {
 			updateMetaInfo(filename, &w.meta)
 		}
@@ -99,7 +99,7 @@ func IndexFile(filename string, contents []byte) error {
 	start := time.Now()
 	fp, err := os.Open(cacheFile)
 	if err != nil {
-		_, w, err := ParseContents(filename, contents, nil)
+		_, w, err := ParseContents(filename, contents, nil, nil)
 		if err != nil {
 			return err
 		}
@@ -112,7 +112,7 @@ func IndexFile(filename string, contents []byte) error {
 		// do not really care about why exactly reading from cache failed
 		os.Remove(cacheFile)
 
-		_, w, err := ParseContents(filename, contents, nil)
+		_, w, err := ParseContents(filename, contents, nil, nil)
 		if err != nil {
 			return err
 		}
diff --git a/src/linter/parser.go b/src/linter/parser.go
index 53e6545..41d1c27 100644
--- a/src/linter/parser.go
+++ b/src/linter/parser.go
@@ -15,6 +15,9 @@ import (
 	"sync/atomic"
 	"time"
 
+	"github.com/karrick/godirwalk"
+	"github.com/quasilyte/regex/syntax"
+
 	"github.com/VKCOM/noverify/src/git"
 	"github.com/VKCOM/noverify/src/inputs"
 	"github.com/VKCOM/noverify/src/lintdebug"
@@ -23,8 +26,6 @@ import (
 	"github.com/VKCOM/noverify/src/php/parser/php7"
 	"github.com/VKCOM/noverify/src/quickfix"
 	"github.com/VKCOM/noverify/src/rules"
-	"github.com/karrick/godirwalk"
-	"github.com/quasilyte/regex/syntax"
 )
 
 type FileInfo struct {
@@ -72,7 +73,7 @@ type ReadCallback func(ch chan FileInfo)
 
 // ParseContents parses specified contents (or file) and returns *RootWalker.
 // Function does not update global meta.
-func ParseContents(filename string, contents []byte, lineRanges []git.LineRange) (rootNode node.Node, w *RootWalker, err error) {
+func ParseContents(filename string, contents []byte, lineRanges []git.LineRange, allowDisabled *regexp.Regexp) (rootNode node.Node, w *RootWalker, err error) {
 	defer func() {
 		if r := recover(); r != nil {
 			s := fmt.Sprintf("Panic while parsing %s: %s\n\nStack trace: %s", filename, r, dbg.Stack())
@@ -113,7 +114,7 @@ func ParseContents(filename string, contents []byte, lineRanges []git.LineRange)
 
 	atomic.AddInt64(&initParseTime, int64(time.Since(start)))
 
-	return analyzeFile(filename, contents, parser, lineRanges)
+	return analyzeFile(filename, contents, parser, lineRanges, allowDisabled)
 }
 
 func cloneRulesForFile(filename string, ruleSet *rules.ScopedSet) *rules.ScopedSet {
@@ -135,7 +136,7 @@ func cloneRulesForFile(filename string, ruleSet *rules.ScopedSet) *rules.ScopedS
 	return &clone
 }
 
-func analyzeFile(filename string, contents []byte, parser *php7.Parser, lineRanges []git.LineRange) (*node.Root, *RootWalker, error) {
+func analyzeFile(filename string, contents []byte, parser *php7.Parser, lineRanges []git.LineRange, allowedDisabled *regexp.Regexp) (*node.Root, *RootWalker, error) {
 	start := time.Now()
 	rootNode := parser.GetRootNode()
 
@@ -166,6 +167,8 @@ func analyzeFile(filename string, contents []byte, parser *php7.Parser, lineRang
 			}),
 			out: &strings.Builder{},
 		},
+
+		allowDisabledRegexp: allowedDisabled,
 	}
 
 	w.InitFromParser(contents, parser)
@@ -466,7 +469,7 @@ func ReadFilesFromGitWithChanges(repo, commitSHA1 string, changes []git.Change)
 }
 
 // ParseFilenames is used to do initial parsing of files.
-func ParseFilenames(readFileNamesFunc ReadCallback) []*Report {
+func ParseFilenames(readFileNamesFunc ReadCallback, allowDisabled *regexp.Regexp) []*Report {
 	start := time.Now()
 	defer func() {
 		lintdebug.Send("Processing time: %s", time.Since(start))
@@ -496,7 +499,7 @@ func ParseFilenames(readFileNamesFunc ReadCallback) []*Report {
 		go func() {
 			var rep []*Report
 			for f := range filenamesCh {
-				rep = append(rep, doParseFile(f, needReports)...)
+				rep = append(rep, doParseFile(f, needReports, allowDisabled)...)
 			}
 			reportsCh <- rep
 			wg.Done()
@@ -512,7 +515,7 @@ func ParseFilenames(readFileNamesFunc ReadCallback) []*Report {
 	return allReports
 }
 
-func doParseFile(f FileInfo, needReports bool) (reports []*Report) {
+func doParseFile(f FileInfo, needReports bool, allowDisabled *regexp.Regexp) (reports []*Report) {
 	var err error
 
 	if DebugParseDuration > 0 {
@@ -526,7 +529,7 @@ func doParseFile(f FileInfo, needReports bool) (reports []*Report) {
 
 	if needReports {
 		var w *RootWalker
-		_, w, err = ParseContents(f.Filename, f.Contents, f.LineRanges)
+		_, w, err = ParseContents(f.Filename, f.Contents, f.LineRanges, allowDisabled)
 		if err == nil {
 			reports = w.GetReports()
 		}
@@ -544,7 +547,7 @@ func doParseFile(f FileInfo, needReports bool) (reports []*Report) {
 
 func InitStubs(readFileNamesFunc ReadCallback) {
 	meta.SetLoadingStubs(true)
-	ParseFilenames(readFileNamesFunc)
+	ParseFilenames(readFileNamesFunc, nil)
 	meta.Info.InitStubs()
 	meta.SetLoadingStubs(false)
 }
diff --git a/src/linter/report.go b/src/linter/report.go
index e3d48bb..3f455b0 100644
--- a/src/linter/report.go
+++ b/src/linter/report.go
@@ -451,6 +451,12 @@ function performance_test() {}`,
   public function d();
 }`,
 		},
+
+		{
+			Name:    "linterError",
+			Default: true,
+			Comment: `Report linter error.`,
+		},
 	}
 
 	for _, info := range allChecks {
@@ -520,7 +526,11 @@ func (r *Report) String() string {
 
 	// No context line for security-level warnings.
 	if r.level == LevelSecurity {
-		return fmt.Sprintf("%s %s at %s:%d", severityNames[r.level], msg, r.filename, r.startLine)
+
+		// To make output stable between platforms, see #572
+		filename := strings.ReplaceAll(r.filename, "\\", "/")
+
+		return fmt.Sprintf("%s %s at %s:%d", severityNames[r.level], msg, filename, r.startLine)
 	}
 
 	contextLn := strings.Builder{}
diff --git a/src/linter/root.go b/src/linter/root.go
index fa6472c..749f172 100644
--- a/src/linter/root.go
+++ b/src/linter/root.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"math"
 	"path/filepath"
+	"regexp"
 	"strconv"
 	"strings"
 
@@ -63,7 +64,10 @@ type RootWalker struct {
 
 	currentClassNode node.Node
 
-	disabledFlag bool // user-defined flag that file should not be linted
+	allowDisabledRegexp        *regexp.Regexp // user-defined flag that files suitable for this regular expression should not be linted
+	allowDisabledRegexpWasUsed bool           // additional flag indicating that the file name has already been checked by regular expression
+	linterDisabled             bool           // flag indicating whether linter is disabled. Flag is set to true only if the file
+	// name matches the pattern and @linter disable was encountered
 
 	// strictTypes is true if file contains `declare(strict_types=1)`.
 	strictTypes bool
@@ -213,7 +217,7 @@ func (d *RootWalker) EnterNode(w walker.Walkable) (res bool) {
 			for _, cs := range *ffs {
 				for _, c := range cs {
 					if c.StringType == freefloating.CommentType {
-						d.handleComment(c)
+						d.handleComment(c, n)
 					}
 				}
 			}
@@ -461,6 +465,13 @@ func (d *RootWalker) Report(n node.Node, level int, checkName, msg string, args
 				return
 			}
 		}
+
+		allowDisabled := false
+
+		if d.allowDisabledRegexp != nil {
+			allowDisabled = d.allowDisabledRegexp.MatchString(d.ctx.st.CurrentFile)
+		}
+
 		d.reports = append(d.reports, &Report{
 			checkName:  checkName,
 			startLn:    string(startLn),
@@ -471,7 +482,7 @@ func (d *RootWalker) Report(n node.Node, level int, checkName, msg string, args
 			filename:   d.ctx.st.CurrentFile,
 			msg:        msg,
 			hash:       hash,
-			isDisabled: d.disabledFlag,
+			isDisabled: allowDisabled,
 		})
 	}
 }
@@ -558,7 +569,7 @@ func (d *RootWalker) reportUndefinedVariable(v node.Node, maybeHave bool) {
 	}
 }
 
-func (d *RootWalker) handleComment(c freefloating.String) {
+func (d *RootWalker) handleComment(c freefloating.String, n node.Node) {
 	if c.StringType != freefloating.CommentType {
 		return
 	}
@@ -574,8 +585,14 @@ func (d *RootWalker) handleComment(c freefloating.String) {
 		}
 
 		for _, p := range ln.(*phpdoc.RawCommentPart).Params {
-			if p == "disable" {
-				d.disabledFlag = true
+
+			if d.allowDisabledRegexp != nil && !d.allowDisabledRegexpWasUsed {
+				d.linterDisabled = d.allowDisabledRegexp.MatchString(d.ctx.st.CurrentFile)
+				d.allowDisabledRegexpWasUsed = true
+			}
+
+			if p == "disable" && !d.linterDisabled {
+				d.Report(nil, LevelInformation, "linterError", "You are not allowed to disable linter")
 			}
 		}
 	}


Test patch:
diff --git a/src/linter/cache_test.go b/src/linter/cache_test.go
index 4fe78f6..fff3ed4 100644
--- a/src/linter/cache_test.go
+++ b/src/linter/cache_test.go
@@ -90,7 +90,7 @@ main();
 `
 
 	runTest := func(iteration int) {
-		_, root, err := ParseContents("cachetest.php", []byte(code), nil)
+		_, root, err := ParseContents("cachetest.php", []byte(code), nil, nil)
 		if err != nil {
 			t.Fatalf("parse error: %v", err)
 		}
diff --git a/src/linttest/basic_test.go b/src/linttest/basic_test.go
index 1e274f8..b9e0d5a 100644
--- a/src/linttest/basic_test.go
+++ b/src/linttest/basic_test.go
@@ -110,10 +110,47 @@ try {
 }
 
 func TestLinterDisable(t *testing.T) {
-	linttest.SimpleNegativeTest(t, `<?php
+	test := linttest.NewSuite(t)
+	// TODO(Petr Makhnev): Here we need the ability to add flags for the file
+	test.AddFile(`<?php
+/** @linter disable */
+$_ = array(1);
+`)
+	test.Expect = []string{
+		`You are not allowed to disable linter`,
+		`Use of old array syntax (use short form instead)`,
+	}
+	test.RunAndMatch()
+}
+
+func TestMultiplyLinterDisable(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
 /** @linter disable */
 $_ = array(1);
+
+/** @linter disable */
+$_ = [
+	1 => 1,
+	1 => 2
+];
+
+/** @linter disable */
+
+/** @linter disable */
+$_ = array(1);
+
 `)
+	test.Expect = []string{
+		`You are not allowed to disable linter`,
+		`Use of old array syntax (use short form instead)`,
+		`You are not allowed to disable linter`,
+		`Duplicate array key '1'`,
+		`You are not allowed to disable linter`,
+		`You are not allowed to disable linter`,
+		`Use of old array syntax (use short form instead)`,
+	}
+	test.RunAndMatch()
 }
 
 func TestKeywordCaseElseif(t *testing.T) {
diff --git a/src/linttest/linttest.go b/src/linttest/linttest.go
index 01be5b1..139561f 100644
--- a/src/linttest/linttest.go
+++ b/src/linttest/linttest.go
@@ -219,7 +219,7 @@ func shuffleFiles(files []TestFile) {
 
 func parseTestFile(t testing.TB, f TestFile) (rootNode node.Node, w *linter.RootWalker) {
 	var err error
-	rootNode, w, err = linter.ParseContents(f.Name, f.Data, nil)
+	rootNode, w, err = linter.ParseContents(f.Name, f.Data, nil, nil)
 	if err != nil {
 		t.Fatalf("could not parse %s: %v", f.Name, err.Error())
 	}