Repository: VKCOM/noverify
Pull request #992 (2021-05-23 17:16:36)

linter: added classMembersOrder checker
`classMembersOrder` checks that all constants and
properties of the class go before methods.

quasilyte: Conflicts.
i582: > Conflicts.

Fixed.

Patch:
diff --git a/src/linter/block_linter.go b/src/linter/block_linter.go
index d7a935f..5aaf3d4 100644
--- a/src/linter/block_linter.go
+++ b/src/linter/block_linter.go
@@ -26,6 +26,9 @@ func (b *blockLinter) enterNode(n ir.Node) {
 	case *ir.ArrayExpr:
 		b.checkArray(n)
 
+	case *ir.ClassStmt:
+		b.checkClass(n)
+
 	case *ir.FunctionCallExpr:
 		b.checkFunctionCall(n)
 
@@ -184,6 +187,43 @@ func (b *blockLinter) enterNode(n ir.Node) {
 	}
 }
 
+func (b *blockLinter) checkClass(class *ir.ClassStmt) {
+	const classMethod = 0
+	const classOtherMember = 1
+
+	var members = make([]int, 0, len(class.Stmts))
+	for _, stmt := range class.Stmts {
+		switch stmt.(type) {
+		case *ir.ClassMethodStmt:
+			members = append(members, classMethod)
+		default:
+			members = append(members, classOtherMember)
+		}
+	}
+
+	var methodsBegin bool
+	for index, member := range members {
+		if member == classMethod {
+			methodsBegin = true
+		} else if methodsBegin {
+			stmt := class.Stmts[index]
+			memberType := ""
+			memberName := ""
+			switch stmt := stmt.(type) {
+			case *ir.ClassConstListStmt:
+				memberType = "Constant"
+				memberName = stmt.Consts[0].(*ir.ConstantStmt).ConstantName.Value
+			case *ir.PropertyListStmt:
+				memberType = "Property"
+				memberName = "$" + stmt.Properties[0].(*ir.PropertyStmt).Variable.Name
+			default:
+				continue
+			}
+			b.report(stmt, LevelError, "classMembersOrder", "%s %s must go before methods in the class %s", memberType, memberName, class.ClassName.Value)
+		}
+	}
+}
+
 func (b *blockLinter) checkForeach(n *ir.ForeachStmt) {
 	b.walker.r.checkKeywordCase(n, "foreach")
 
diff --git a/src/linter/report.go b/src/linter/report.go
index c4fd15d..47d6623 100644
--- a/src/linter/report.go
+++ b/src/linter/report.go
@@ -672,6 +672,23 @@ function f(array $a) {}`,
 			After:    `strpos($s, '/');`,
 		},
 
+		{
+			Name:     "classMembersOrder",
+			Default:  false,
+			Quickfix: false,
+			Comment:  `Report the wrong order of the class members.`,
+			Before: `class A {
+  public function func() {}
+  const B = 1;
+  public $c = 2;
+}`,
+			After: `class A {
+  const B = 1;
+  public $c = 2;
+  public function func() {}
+}`,
+		},
+
 		{
 			Name:     "varShadow",
 			Default:  true,


Test patch:
diff --git a/src/tests/checkers/misspell_test.go b/src/tests/checkers/misspell_test.go
index 0558367..aa4c3c0 100644
--- a/src/tests/checkers/misspell_test.go
+++ b/src/tests/checkers/misspell_test.go
@@ -28,14 +28,14 @@ class c1 {
   const Foo = 0;
 
   /**
-   * This method is never called, this is why it's inexpencive.
+   * This property is not inefficeint.
    */
-  private static function secret() {}
+  private $prop = 1;
 
   /**
-   * This property is not inefficeint.
+   * This method is never called, this is why it's inexpencive.
    */
-  private $prop = 1;
+  private static function secret() {}
 }
 `)
 	test.Expect = []string{
diff --git a/src/tests/checkers/oop_test.go b/src/tests/checkers/oop_test.go
index 06e5c2d..c9d34f0 100644
--- a/src/tests/checkers/oop_test.go
+++ b/src/tests/checkers/oop_test.go
@@ -1529,3 +1529,98 @@ function a1(Foo $a, foo $b, boo $c) {}
 	}
 	test.RunAndMatch()
 }
+
+func TestClassComponentsOrder(t *testing.T) {
+	test := linttest.NewSuite(t)
+	test.AddFile(`<?php
+class Foo {
+  public function f() {}
+
+  const A = 10;
+  public string $a = "";
+}
+
+class Foo1 {
+  const A = 10;
+
+  public function f() {}
+
+  public string $a = "";
+}
+
+class Foo2 {
+  const A = 10;
+
+  public function f() {}
+
+  public static string $a = "";
+
+  public function f1() {}
+}
+
+class Foo3 {
+  public function f() {}
+
+  public string $a = "";
+
+  public static function f1() {}
+
+  const A = 10;
+}
+`)
+
+	test.Expect = []string{
+		`Constant A must go before methods in the class Foo`,
+		`Property $a must go before methods in the class Foo`,
+		`Property $a must go before methods in the class Foo1`,
+		`Property $a must go before methods in the class Foo2`,
+		`Property $a must go before methods in the class Foo3`,
+		`Constant A must go before methods in the class Foo3`,
+	}
+	linttest.RunFilterMatch(test, "classMembersOrder")
+}
+
+func TestClassComponentsOrderGood(t *testing.T) {
+	linttest.SimpleNegativeTest(t, `<?php
+class Foo {
+  const A = 10;
+  public string $a = "";
+
+  /** */
+  public function f() {}
+}
+
+class Foo1 {
+  const A = 10;
+  public string $a = "";
+}
+
+class Foo2 {}
+
+class Foo3 {
+  const A = 10;
+
+  /** */
+  public function f() {}
+}
+
+class Foo4 {
+  public string $a = "";
+
+  /** */
+  public function f() {}
+}
+
+class Foo5 {
+  const A = 10, B = 100;
+  public string $a = "", $b = "1";
+  public string $c = "", $d = "1";
+
+  /** */
+  public function f() {}
+
+  /** */
+  public function f1() {}
+}
+`)
+}
diff --git a/src/tests/golden/testdata/math/golden.txt b/src/tests/golden/testdata/math/golden.txt
index 04aab14..936f068 100644
--- a/src/tests/golden/testdata/math/golden.txt
+++ b/src/tests/golden/testdata/math/golden.txt
@@ -16,3 +16,33 @@ MAYBE   trailingComma: last element in a multi-line array must have a trailing c
 MAYBE   trailingComma: last element in a multi-line array must have a trailing comma at testdata/math/src/Internal/Calculator/NativeCalculator.php:187
                     (string) $r
                     ^^^^^^^^^^^
+ERROR   classMembersOrder: Constant UNNECESSARY must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:33
+    public const UNNECESSARY = 0;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant UP must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:41
+    public const UP = 1;
+    ^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant DOWN must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:49
+    public const DOWN = 2;
+    ^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant CEILING must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:57
+    public const CEILING = 3;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant FLOOR must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:65
+    public const FLOOR = 4;
+    ^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant HALF_UP must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:73
+    public const HALF_UP = 5;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant HALF_DOWN must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:80
+    public const HALF_DOWN = 6;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant HALF_CEILING must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:87
+    public const HALF_CEILING = 7;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant HALF_FLOOR must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:94
+    public const HALF_FLOOR = 8;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant HALF_EVEN must go before methods in the class RoundingMode at testdata/math/src/RoundingMode.php:106
+    public const HALF_EVEN = 9;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/mustache/golden.txt b/src/tests/golden/testdata/mustache/golden.txt
index 4f9fd95..452b704 100644
--- a/src/tests/golden/testdata/mustache/golden.txt
+++ b/src/tests/golden/testdata/mustache/golden.txt
@@ -25,6 +25,75 @@ WARNING unused: Variable keystr is unused (use $_ to ignore this inspection) at
 MAYBE   callSimplify: Could simplify to $id[0] at testdata/mustache/src/Mustache/Compiler.php:646
             if (substr($id, 0, 1) === '.') {
                 ^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant KLASS must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:184
+    const KLASS = '<?php
+    ^^^^^^^
+ERROR   classMembersOrder: Constant KLASS_NO_LAMBDAS must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:202
+    const KLASS_NO_LAMBDAS = '<?php
+    ^^^^^^^
+ERROR   classMembersOrder: Constant STRICT_CALLABLE must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:215
+    const STRICT_CALLABLE = 'protected $strictCallables = true;';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant BLOCK_VAR must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:237
+    const BLOCK_VAR = '
+    ^^
+ERROR   classMembersOrder: Constant BLOCK_VAR_ELSE must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:244
+    const BLOCK_VAR_ELSE = '} else {%s';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant BLOCK_ARG must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:271
+    const BLOCK_ARG = '%s => array($this, \'block%s\'),';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant BLOCK_FUNCTION must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:295
+    const BLOCK_FUNCTION = '
+    ^^
+ERROR   classMembersOrder: Constant SECTION_CALL must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:323
+    const SECTION_CALL = '
+    ^^
+ERROR   classMembersOrder: Constant SECTION must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:329
+    const SECTION = '
+    ^^
+ERROR   classMembersOrder: Constant INVERTED_SECTION must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:398
+    const INVERTED_SECTION = '
+    ^^
+ERROR   classMembersOrder: Constant PARTIAL_INDENT must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:425
+    const PARTIAL_INDENT = ', $indent . %s';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant PARTIAL must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:426
+    const PARTIAL = '
+    ^^
+ERROR   classMembersOrder: Constant PARENT must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:456
+    const PARENT = '
+    ^^
+ERROR   classMembersOrder: Constant PARENT_NO_CONTEXT must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:465
+    const PARENT_NO_CONTEXT = '
+    ^^
+ERROR   classMembersOrder: Constant VARIABLE must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:508
+    const VARIABLE = '
+    ^^
+ERROR   classMembersOrder: Constant FILTER must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:533
+    const FILTER = '
+    ^^
+ERROR   classMembersOrder: Constant LINE must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:564
+    const LINE = '$buffer .= "\n";';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant TEXT must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:565
+    const TEXT = '$buffer .= %s%s;';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant DEFAULT_ESCAPE must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:607
+    const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant CUSTOM_ESCAPE must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:608
+    const CUSTOM_ESCAPE  = 'call_user_func($this->mustache->getEscape(), %s)';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant IS_CALLABLE must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:658
+    const IS_CALLABLE        = '!is_string(%s) && is_callable(%s)';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant STRICT_IS_CALLABLE must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:659
+    const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant LINE_INDENT must go before methods in the class Mustache_Compiler at testdata/mustache/src/Mustache/Compiler.php:675
+    const LINE_INDENT = '$indent . ';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 MAYBE   callSimplify: Could simplify to $this->stack[] = $value at testdata/mustache/src/Mustache/Context.php:39
         array_push($this->stack, $value);
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/src/tests/golden/testdata/parsedown/golden.txt b/src/tests/golden/testdata/parsedown/golden.txt
index f08589a..4fc1138 100644
--- a/src/tests/golden/testdata/parsedown/golden.txt
+++ b/src/tests/golden/testdata/parsedown/golden.txt
@@ -148,3 +148,57 @@ WARNING unused: Variable val is unused (use $_ to ignore this inspection) at tes
 MAYBE   typeHint: specify the type for the parameter $Element in phpdoc, 'array' type hint too generic at testdata/parsedown/parsedown.php:1900
     protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $breaksEnabled must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:66
+    protected $breaksEnabled;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $markupEscaped must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:75
+    protected $markupEscaped;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $urlsLinked must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:84
+    protected $urlsLinked = true;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $safeMode must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:93
+    protected $safeMode;
+    ^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $strictMode must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:102
+    protected $strictMode;
+    ^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $safeLinksWhitelist must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:104
+    protected $safeLinksWhitelist = array(
+    ^^
+ERROR   classMembersOrder: Property $BlockTypes must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:126
+    protected $BlockTypes = array(
+    ^^
+ERROR   classMembersOrder: Property $unmarkedBlockTypes must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:154
+    protected $unmarkedBlockTypes = array(
+    ^^
+ERROR   classMembersOrder: Property $InlineTypes must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1109
+    protected $InlineTypes = array(
+    ^^
+ERROR   classMembersOrder: Property $inlineMarkerList must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1124
+    protected $inlineMarkerList = '!*_&[:<`~\\';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $instances must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1952
+    private static $instances = array();
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $DefinitionData must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1958
+    protected $DefinitionData;
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $specialCharacters must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1963
+    protected $specialCharacters = array(
+    ^^
+ERROR   classMembersOrder: Property $StrongRegex must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1967
+    protected $StrongRegex = array(
+    ^^
+ERROR   classMembersOrder: Property $EmRegex must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1972
+    protected $EmRegex = array(
+    ^^
+ERROR   classMembersOrder: Property $regexHtmlAttribute must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1977
+    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $voidElements must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1979
+    protected $voidElements = array(
+    ^^
+ERROR   classMembersOrder: Property $textLevelElements must go before methods in the class Parsedown at testdata/parsedown/parsedown.php:1983
+    protected $textLevelElements = array(
+    ^^
diff --git a/src/tests/golden/testdata/qrcode/golden.txt b/src/tests/golden/testdata/qrcode/golden.txt
index cc4cdf3..f31cf02 100644
--- a/src/tests/golden/testdata/qrcode/golden.txt
+++ b/src/tests/golden/testdata/qrcode/golden.txt
@@ -64,3 +64,30 @@ MAYBE   callSimplify: Could simplify to $group[0] at testdata/qrcode/qrcode.php:
 MAYBE   callSimplify: Could simplify to $group[1] at testdata/qrcode/qrcode.php:449
       $c2    = ord(substr($group, 1, 1));
                    ^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $qr_capacity must go before methods in the class QRCode at testdata/qrcode/qrcode.php:890
+  private $qr_capacity = [
+  ^^
+ERROR   classMembersOrder: Property $qr_ec_params must go before methods in the class QRCode at testdata/qrcode/qrcode.php:943
+  private $qr_ec_params = [
+  ^^
+ERROR   classMembersOrder: Property $qr_ec_polynomials must go before methods in the class QRCode at testdata/qrcode/qrcode.php:1106
+  private $qr_ec_polynomials = [
+  ^^
+ERROR   classMembersOrder: Property $qr_log must go before methods in the class QRCode at testdata/qrcode/qrcode.php:1162
+  private $qr_log = [
+  ^^
+ERROR   classMembersOrder: Property $qr_exp must go before methods in the class QRCode at testdata/qrcode/qrcode.php:1197
+  private $qr_exp = [
+  ^^
+ERROR   classMembersOrder: Property $qr_remainder_bits must go before methods in the class QRCode at testdata/qrcode/qrcode.php:1232
+  private $qr_remainder_bits = [
+  ^^
+ERROR   classMembersOrder: Property $qr_alignment_patterns must go before methods in the class QRCode at testdata/qrcode/qrcode.php:1237
+  private $qr_alignment_patterns = [
+  ^^
+ERROR   classMembersOrder: Property $qr_format_info must go before methods in the class QRCode at testdata/qrcode/qrcode.php:1282
+  private $qr_format_info = [
+  ^^
+ERROR   classMembersOrder: Property $qr_version_info must go before methods in the class QRCode at testdata/qrcode/qrcode.php:1318
+  private $qr_version_info = [
+  ^^
diff --git a/src/tests/golden/testdata/underscore/golden.txt b/src/tests/golden/testdata/underscore/golden.txt
index 540ed54..c47c9bb 100644
--- a/src/tests/golden/testdata/underscore/golden.txt
+++ b/src/tests/golden/testdata/underscore/golden.txt
@@ -178,3 +178,45 @@ MAYBE   arraySyntax: Use of old array syntax (use short form instead) at testdat
 ERROR   unused: foreach key $k is unused, can simplify $k => $v to just $v at testdata/underscore/underscore.php:1107
       foreach($caller_args as $k=>$v) {
                               ^^
+ERROR   classMembersOrder: Property $_uniqueId must go before methods in the class __ at testdata/underscore/underscore.php:817
+  public $_uniqueId = -1;
+  ^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $_mixins must go before methods in the class __ at testdata/underscore/underscore.php:839
+  private $_mixins = array();
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant TEMPLATE_OPEN_TAG must go before methods in the class __ at testdata/underscore/underscore.php:868
+  const TEMPLATE_OPEN_TAG = '760e7dab2836853c63805033e514668301fa9c47';
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant TEMPLATE_CLOSE_TAG must go before methods in the class __ at testdata/underscore/underscore.php:869
+  const TEMPLATE_CLOSE_TAG= 'd228a8fa36bd7db108b01eddfb03a30899987a2b';
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant TEMPLATE_DEFAULT_EVALUATE must go before methods in the class __ at testdata/underscore/underscore.php:871
+  const TEMPLATE_DEFAULT_EVALUATE   = '/<%([\s\S]+?)%>/';
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant TEMPLATE_DEFAULT_INTERPOLATE must go before methods in the class __ at testdata/underscore/underscore.php:872
+  const TEMPLATE_DEFAULT_INTERPOLATE= '/<%=([\s\S]+?)%>/';
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Constant TEMPLATE_DEFAULT_ESCAPE must go before methods in the class __ at testdata/underscore/underscore.php:873
+  const TEMPLATE_DEFAULT_ESCAPE     = '/<%-([\s\S]+?)%>/';
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $_template_settings must go before methods in the class __ at testdata/underscore/underscore.php:874
+  public $_template_settings = array(
+  ^^
+ERROR   classMembersOrder: Property $_memoized must go before methods in the class __ at testdata/underscore/underscore.php:957
+  public $_memoized = array();
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $_throttled must go before methods in the class __ at testdata/underscore/underscore.php:986
+  public $_throttled = array();
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $_onced must go before methods in the class __ at testdata/underscore/underscore.php:1012
+  public $_onced = array();
+  ^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $_aftered must go before methods in the class __ at testdata/underscore/underscore.php:1057
+  public $_aftered = array();
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $_instance must go before methods in the class __ at testdata/underscore/underscore.php:1075
+  private static $_instance;
+  ^^^^^^^^^^^^^^^^^^^^^^^^^^
+ERROR   classMembersOrder: Property $_wrapped must go before methods in the class __ at testdata/underscore/underscore.php:1087
+  public $_wrapped; // Value passed from one chained method to the next
+  ^^^^^^^^^^^^^^^^^