Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Type Safety and Compilation Checks: How Static Analysis Can Benefit Your Team

Type Safety and Compilation Checks: How Static Analysis Can Benefit Your Team

Numerous technologies exist for creating web applications, with PHP and JavaScript being the most prevalent. Their dynamic typing and runtime execution capabilities allow for a seamless transition from prototype to finished product. However, as apps grow in complexity and significance in our everyday lives, the absence of type safety and compilation checks might be felt. In this presentation, we will explore the concept of static analysis and its potential to enhance your team's software development process.

Hunter Skrasek

April 13, 2023
Tweet

More Decks by Hunter Skrasek

Other Decks in Programming

Transcript

  1. var_dump(createUser('Franke', true)); => User {#2674 +name: "Franke", +age: 1, }

    var_dump(createUser(42, 'age)); => User {#2670 +name: "42", +age: 0, }
  2. ------ ------------------------------------------------------ ------------------------------------------------------------- ------------------- Line ValueObjects/PhoneIntelligence.php ------ ----------------------------------------------------- ------------------------------------------------------------- --------------------

    45 Parameter $lineType of class App\ValueObjects\PhoneIntelligence constructor expects App\Enum\LineType, App\Enum\LineType|null given. 79 Method App\ValueObjects\PhoneIntelligence::toJson() should return string but returns string|false. ------ ----------------------------------------------------- ------------------------------------------------------------- --------------------
  3. <?php declare(strict_types=1); namespace App\ValueObjects; class PhoneIntelligence implements Arrayable, Castable, Jsonable

    { public function __construct( private readonly ?string $formatted, private readonly ?string $carrier, private readonly LineType $lineType = LineType::Unknown ) { } public static function castUsing(array $arguments): CastsAttributes { return new class implements CastsAttributes { public function get($model, string $key, $value, array $attributes): PhoneIntelligence { return new PhoneIntelligence( formatted: $attributes['phone_formatted'], carrier: $attributes['phone_carrier'], lineType: LineType::tryFrom($attributes['phone_type'] ?? 'unknown'), ); } }; } public function toJson($options = 0) { return json_encode($this->toArray(), $options); } }
  4. <?php declare(strict_types=1); namespace App\ValueObjects; class PhoneIntelligence implements Arrayable, Castable, Jsonable

    { public function __construct( private readonly ?string $formatted, private readonly ?string $carrier, private readonly LineType $lineType = LineType::Unknown ) { } public static function castUsing(array $arguments): CastsAttributes { return new class implements CastsAttributes { public function get($model, string $key, $value, array $attributes): PhoneIntelligence { return new PhoneIntelligence( formatted: $attributes['phone_formatted'], carrier: $attributes['phone_carrier'], lineType: LineType::from($attributes['phone_type'] ?? 'unknown'), ); } }; } public function toJson($options = 0) { return json_encode($this->toArray(), JSON_THROW_ON_ERROR | $options); } }
  5. interface Account { id: number displayName: string version: 1 }

    function welcome(user: Account) { console.log(user.id) } type Result = "pass" | "fail" function verify(result: Result) { if (result === "pass") { console.log("Passed") } else { console.log("Failed") } }
  6. - run: name: "Perform a static analysis of application" command:

    | set -x git diff origin/master... > ~/test-results/phpstan/diff.txt if [ ! -s ~/test-results/phpstan/files.txt ]; then exit 0 fi docker compose run --rm app ./vendor/bin/phpstan analyse \ --memory-limit=-1 --error-format=junit --no-interaction --no-progress --no-ansi 1> ~/test-results/phpstan/junit.xml || true docker compose run --rm app ./vendor/bin/phpstan analyse \ --memory-limit=-1 --no-interaction --no-progress --no-ansi > ~/test- results/phpstan/phpstan.txt docker compose run --rm -v ~/test-results/phpstan:/test-reports app ./vendor/bin/diffFilter \ --phpstan /test-reports/diff.txt /test-reports/phpstan.txt 100
  7. npm i typescript import axios, {AxiosResponse, AxiosStatic} from 'axios'; import

    {API_DOMAIN} from './constants'; import {Homeowner} from './homeowner'; import {Project} from './project'; import Contractor from './contractor'; import ExternalMatch from './external-match'; import {parse} from 'date-fns'; import * as _ from 'lodash'; class ModernizeClient { private axios: AxiosStatic; constructor() { this.axios = axios; this.axios.defaults.baseURL = API_DOMAIN; } public projectDetails(projectId: string): Promise<Project> { return axios.get('/v1/projects/' + projectId) .then(response => { let responseData = response.data.data; let homeowner = new Homeowner(responseData.homeowner.data.firstName, responseData.homeowner.data.lastName, responseData.homeowner.data.phoneNumber); return Promise.resolve( new Project(homeowner, responseData.projectType, parse(responseData.dateCreated), parse(responseData.startDate)) ); }); } } export {ModernizeClient}
  8. ESLint npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint . --ext

    .ts { "root": true, "parser": "@typescript- eslint/parser", "plugins": [ "@typescript-eslint" ], "extends": [ "eslint:recommended", "plugin:@typescript- eslint/eslint-recommended", "plugin:@typescript- eslint/recommended" ] }
  9. PHP PHPStan vendor/bin/phpstan analyse -- level 9 \ --configuration phpstan.neon

    \ src/ tests/ --generate-baseline Psalm vendor/bin/psalm --set- baseline=your-baseline.xml vendor/bin/psalm --update- baseline Configuration includes: - phpstan-baseline.neon parameters: # your usual configuration options <?xml version="1.0"?> <psalm ... errorBaseline="./path/to/your- baseline.xml" > ... </psalm>
  10. { "compilerOptions": { "target": "esnext", "module": "commonjs", "allowJs": true, "checkJs":

    false, "outDir": "dist", "rootDir": ".", "strict": false, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ "strictNullChecks": true, "resolveJsonModule": true, "sourceMap": true, "baseUrl": ".", "paths": { "*": [ "*", "src/*", "src/setup/*", "src/logic/*", "src/models/*", "config/*" ] }, }, "exclude": ["node_modules", "dist"], "include": [ "./src", "./test", "./*", "./config" ] }