Repository: tightenco/tlint (php)

Issue:
NoStringInterpolationWithoutBraces does not trigger for object properties
Working as intended (triggers lint)
```php
echo "$a";
```

Not working as intended (does not trigger lint)
```php
echo "$object->prop";
```

Pull request:
Fix String Interpolation
Fixes #22.

Patch:
diff --git a/src/Linters/NoStringInterpolationWithoutBraces.php b/src/Linters/NoStringInterpolationWithoutBraces.php
index ef62e63..2d6f938 100644
--- a/src/Linters/NoStringInterpolationWithoutBraces.php
+++ b/src/Linters/NoStringInterpolationWithoutBraces.php
@@ -17,11 +17,18 @@ class NoStringInterpolationWithoutBraces extends BaseLinter
         $traverser = new NodeTraverser;
 
         $visitor = new FindingVisitor(function (Node $node) use ($parser) {
-            if ($node instanceof Node\Scalar\Encapsed && $node->parts[0] instanceof Node\Expr\Variable) {
-                $line = $this->getCodeLine($node->getStartLine());
-                $name = $node->parts[0]->name;
-
-                return ! str_contains($line, "{\${$name}}");
+            if ($node instanceof Node\Scalar\Encapsed) {
+                if ($node->parts[0] instanceof Node\Expr\Variable) {
+                    $line = $this->getCodeLine($node->getStartLine());
+                    $name = $node->parts[0]->name;
+    
+                    return ! str_contains($line, "{\${$name}}");
+                } elseif ($node->parts[0] instanceof Node\Expr\PropertyFetch) {
+                    $line = $this->getCodeLine($node->getStartLine());
+                    $propertyFetchString = $this->constructPropertyFetchString($node->parts[0]);
+                    
+                    return ! str_contains($line, "{\${$propertyFetchString}");
+                }
             }
 
             return false;
@@ -33,4 +40,12 @@ class NoStringInterpolationWithoutBraces extends BaseLinter
 
         return $visitor->getFoundNodes();
     }
+    
+    private function constructPropertyFetchString($next, $string = '') {
+        if (property_exists($next, 'var')) {
+            return $this->constructPropertyFetchString($next->var, $string . $next->name->name);
+        }
+
+        return implode('->', str_split(strrev($string . $next->name)));
+    }
 }


Test patch:
diff --git a/tests/Linters/NoStringInterpolationWithoutBracesTest.php b/tests/Linters/NoStringInterpolationWithoutBracesTest.php
index 3da68e5..9ddff20 100644
--- a/tests/Linters/NoStringInterpolationWithoutBracesTest.php
+++ b/tests/Linters/NoStringInterpolationWithoutBracesTest.php
@@ -42,4 +42,71 @@ file;
 
         $this->assertEmpty($lints);
     }
+    
+    /** @test */
+    public function does_not_trigger_for_object_properties_with_braces()
+    {
+        $file = <<<file
+<?php
+
+\$next = "{\$a->b}";
+file;
+        
+        $lints = (new TLint)->lint(
+            new NoStringInterpolationWithoutBraces($file)
+        );
+        
+        $this->assertEmpty($lints);
+    }
+    
+    /** @test */
+    public function does_trigger_for_object_properties_without_braces()
+    {
+        $file = <<<file
+<?php
+
+\$next = "\$a->b";
+file;
+        
+        $lints = (new TLint)->lint(
+            new NoStringInterpolationWithoutBraces($file)
+        );
+    
+        $this->assertEquals(3, $lints[0]->getNode()->getLine());
+    }
+    
+    /** @test */
+    public function does_trigger_for_nested_object_properties_without_braces()
+    {
+        $file = <<<file
+<?php
+
+\$next = "\$a->b->c";
+file;
+        
+        $lints = (new TLint)->lint(
+            new NoStringInterpolationWithoutBraces($file)
+        );
+        
+        $this->assertEquals(3, $lints[0]->getNode()->getLine());
+    }
+    
+    /**
+     * @test
+     * @group foo
+     */
+    public function does_not_trigger_for_nested_object_properties_with_braces()
+    {
+        $file = <<<file
+<?php
+
+\$next = "{\$a->b->c->d->e}";
+file;
+        
+        $lints = (new TLint)->lint(
+            new NoStringInterpolationWithoutBraces($file)
+        );
+        
+        $this->assertEmpty($lints);
+    }
 }