create(ParserFactory::PREFER_PHP7); $config = new TestConfig(); } /** * @return void */ public function setUp() { FileChecker::clearCache(); $this->project_checker = new \Psalm\Checker\ProjectChecker(); $this->file_checker = new FileChecker('somefile.php', $this->project_checker); $this->file_checker->context = new Context(); $this->project_checker = new ProjectChecker(); } /** * @return void */ public function testNotNull() { $this->assertEquals( 'MyObject', (string) TypeChecker::reconcileTypes('!null', Type::parseString('MyObject'), null, $this->file_checker) ); $this->assertEquals( 'MyObject', (string) TypeChecker::reconcileTypes('!null', Type::parseString('MyObject|null'), null, $this->file_checker) ); $this->assertEquals( 'MyObject|false', (string) TypeChecker::reconcileTypes('!null', Type::parseString('MyObject|false'), null, $this->file_checker) ); $this->assertEquals( 'mixed', (string) TypeChecker::reconcileTypes('!null', Type::parseString('mixed'), null, $this->file_checker) ); } /** * @return void */ public function testNotEmpty() { $this->assertEquals( 'MyObject', (string) TypeChecker::reconcileTypes('!empty', Type::parseString('MyObject'), null, $this->file_checker) ); $this->assertEquals( 'MyObject', (string) TypeChecker::reconcileTypes('!empty', Type::parseString('MyObject|null'), null, $this->file_checker) ); $this->assertEquals( 'MyObject', (string) TypeChecker::reconcileTypes('!empty', Type::parseString('MyObject|false'), null, $this->file_checker) ); $this->assertEquals( 'mixed', (string) TypeChecker::reconcileTypes('!empty', Type::parseString('mixed'), null, $this->file_checker) ); // @todo in the future this should also work /* $this->assertEquals( 'MyObject|true', (string) TypeChecker::reconcileTypes('!empty', Type::parseString('MyObject|bool')) ); */ } /** * @return void */ public function testNull() { $this->assertEquals( 'null', (string) TypeChecker::reconcileTypes('null', Type::parseString('MyObject|null'), null, $this->file_checker) ); $this->assertEquals( 'null', (string) TypeChecker::reconcileTypes('null', Type::parseString('mixed'), null, $this->file_checker) ); } /** * @return void */ public function testEmpty() { $this->assertEquals( 'null', (string) TypeChecker::reconcileTypes('empty', Type::parseString('MyObject'), null, $this->file_checker) ); $this->assertEquals( 'false', (string) TypeChecker::reconcileTypes('empty', Type::parseString('MyObject|false'), null, $this->file_checker) ); $this->assertEquals( 'false', (string) TypeChecker::reconcileTypes('empty', Type::parseString('MyObject|bool'), null, $this->file_checker) ); $this->assertEquals( 'mixed', (string) TypeChecker::reconcileTypes('empty', Type::parseString('mixed'), null, $this->file_checker) ); /** @var Type\Union */ $reconciled = TypeChecker::reconcileTypes('empty', Type::parseString('bool'), null, $this->file_checker); $this->assertEquals('false', (string) $reconciled); $this->assertInstanceOf('Psalm\Type\Atomic', $reconciled->types['false']); } /** * @return void */ public function testNotMyObject() { $this->assertEquals( 'bool', (string) TypeChecker::reconcileTypes('!MyObject', Type::parseString('MyObject|bool'), null, $this->file_checker) ); $this->assertEquals( 'null', (string) TypeChecker::reconcileTypes('!MyObject', Type::parseString('MyObject|null'), null, $this->file_checker) ); $this->assertEquals( 'MyObjectB', (string) TypeChecker::reconcileTypes('!MyObjectA', Type::parseString('MyObjectA|MyObjectB'), null, $this->file_checker) ); } /** * @return void */ public function testMyObject() { $this->assertEquals( 'MyObject', (string) TypeChecker::reconcileTypes('MyObject', Type::parseString('MyObject|bool'), null, $this->file_checker) ); $this->assertEquals( 'MyObjectA', (string) TypeChecker::reconcileTypes('MyObjectA', Type::parseString('MyObjectA|MyObjectB'), null, $this->file_checker) ); } /** * @return void */ public function testArray() { $this->assertEquals( 'array', (string) TypeChecker::reconcileTypes('array', Type::parseString('array|null'), null, $this->file_checker) ); } /** * @return void */ public function test2DArray() { $this->assertEquals( 'array>', (string) TypeChecker::reconcileTypes('array', Type::parseString('array>|null'), null, $this->file_checker) ); } /** * @return void */ public function testArrayContains() { $this->assertTrue( TypeChecker::isContainedBy( Type::parseString('array'), Type::parseString('array'), $this->file_checker ) ); $this->assertTrue( TypeChecker::isContainedBy( Type::parseString('array'), Type::parseString('array'), $this->file_checker ) ); } /** * @return void */ public function testUnionContains() { $this->assertTrue( TypeChecker::isContainedBy( Type::parseString('string'), Type::parseString('string|false'), $this->file_checker ) ); $this->assertTrue( TypeChecker::isContainedBy( Type::parseString('false'), Type::parseString('string|false'), $this->file_checker ) ); } /** * @return void */ public function testNumeric() { $this->assertEquals( 'string', (string) TypeChecker::reconcileTypes('numeric', Type::parseString('string'), null, $this->file_checker) ); } /** * @return void */ public function testNegateFormula() { $formula = [ new Clause(['$a' => ['!empty']]) ]; $negated_formula = TypeChecker::negateFormula($formula); $this->assertSame(1, count($negated_formula)); $this->assertSame(['$a' => ['empty']], $negated_formula[0]->possibilities); $formula = [ new Clause(['$a' => ['!empty'], '$b' => ['!empty']]) ]; $negated_formula = TypeChecker::negateFormula($formula); $this->assertSame(2, count($negated_formula)); $this->assertSame(['$a' => ['empty']], $negated_formula[0]->possibilities); $this->assertSame(['$b' => ['empty']], $negated_formula[1]->possibilities); $formula = [ new Clause(['$a' => ['!empty']]), new Clause(['$b' => ['!empty']]), ]; $negated_formula = TypeChecker::negateFormula($formula); $this->assertSame(1, count($negated_formula)); $this->assertSame(['$a' => ['empty'], '$b' => ['empty']], $negated_formula[0]->possibilities); $formula = [ new Clause(['$a' => ['int', 'string'], '$b' => ['!empty']]) ]; $negated_formula = TypeChecker::negateFormula($formula); $this->assertSame(3, count($negated_formula)); $this->assertSame(['$a' => ['!int']], $negated_formula[0]->possibilities); $this->assertSame(['$a' => ['!string']], $negated_formula[1]->possibilities); $this->assertSame(['$b' => ['empty']], $negated_formula[2]->possibilities); } /** * @return void */ public function testContainsClause() { $this->assertTrue( (new Clause( [ '$a' => ['!empty'], '$b' => ['!empty'] ] ))->contains( new Clause( [ '$a' => ['!empty'] ] ) ) ); $this->assertFalse( (new Clause( [ '$a' => ['!empty'] ] ))->contains( new Clause( [ '$a' => ['!empty'], '$b' => ['!empty'] ] ) ) ); } /** * @return void */ public function testSimplifyCNF() { $formula = [ new Clause(['$a' => ['!empty']]), new Clause(['$a' => ['empty'], '$b' => ['empty']]) ]; $simplified_formula = TypeChecker::simplifyCNF($formula); $this->assertSame(2, count($simplified_formula)); $this->assertSame(['$a' => ['!empty']], $simplified_formula[0]->possibilities); $this->assertSame(['$b' => ['empty']], $simplified_formula[1]->possibilities); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage TypeDoesNotContainType * @return void */ public function testMakeNonNullableNull() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods($context); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage TypeDoesNotContainType * @return void */ public function testMakeInstanceOfThingInElseif() { $stmts = self::$parser->parse(' 5 ? new A() : new B(); if ($a instanceof A) { } elseif ($a instanceof C) { } '); $file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods($context); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage FailedTypeResolution * @return void */ public function testFailedTypeResolution() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods($context); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage FailedTypeResolution * @return void */ public function testFailedTypeResolutionWithDocblock() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods($context); } /** * @return void */ public function testTypeResolutionFromDocblock() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods($context); } /** * @return void */ public function testTypeResolutionFromDocblockInside() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods($context); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage FailedTypeResolution * @return void */ public function testTypeResolutionFromDocblockAndInstanceof() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods($context); } /** * @return void */ public function testNotInstanceOf() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $context->vars_in_scope['$a'] = Type::parseString('A'); $file_checker->visitAndAnalyzeMethods($context); $this->assertEquals('null|A', (string) $context->vars_in_scope['$out']); } /** * @return void */ public function testNotInstanceOfProperty() { $stmts = self::$parser->parse('foo = new B(); } } $a = new A(); $out = null; if ($a->foo instanceof C) { // do something } else { $out = $a->foo; } '); $file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts); $context = new Context(); $context->vars_in_scope['$a'] = Type::parseString('A'); $file_checker->visitAndAnalyzeMethods($context); $this->assertEquals('null|B', (string) $context->vars_in_scope['$out']); } /** * @return void */ public function testNotInstanceOfPropertyElseif() { $stmts = self::$parser->parse('foo)) { } elseif ($a->foo instanceof C) { // do something } else { $out = $a->foo; } '); $file_checker = new FileChecker('somefile.php', $this->project_checker, $stmts); $context = new Context(); $context->vars_in_scope['$a'] = Type::parseString('A'); $file_checker->visitAndAnalyzeMethods($context); $this->assertEquals('null|B', (string) $context->vars_in_scope['$out']); } /** * @return void */ public function testTypeArguments() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods($context); $this->assertEquals('int', (string) $context->vars_in_scope['$a']); $this->assertEquals('int', (string) $context->vars_in_scope['$b']); $this->assertEquals('string', (string) $context->vars_in_scope['$c']); $this->assertEquals('string', (string)$context->vars_in_scope['$hours']); $this->assertEquals('string', (string)$context->vars_in_scope['$minutes']); $this->assertEquals('string', (string)$context->vars_in_scope['$seconds']); } /** * @expectedException \Psalm\Exception\CodeException * @expectedExceptionMessage TypeDoesNotContainType * @return void */ public function testTypeTransformation() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods($context); } /** * @return void */ public function testTypeRefinementWithIsNumeric() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods(); } /** * @return void */ public function testTypeRefinementWithIsNumericAndIsString() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods(); } /** * @return void */ public function testUpdateMultipleIssetVars() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods(); } /** * @return void */ public function testUpdateMultipleIssetVarsWithVariableOffset() { $stmts = self::$parser->parse('project_checker, $stmts); $context = new Context(); $file_checker->visitAndAnalyzeMethods(); } }