[
  {
    "path": ".editorconfig",
    "content": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at https://editorconfig.org\nroot = true\n\n[*]\nindent_style = tab\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.bat]\nend_of_line = crlf\n\n[*.yml]\nindent_style = space\nindent_size = 2\n\n[*.md]\nindent_style = space\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Define the line ending behavior of the different file extensions\n# Set default behavior, in case users don't have core.autocrlf set.\n* text text=auto eol=lf\n\n*.jpg binary\n*.png binary\n\n# Remove files for archives generated using `git archive`\nphpunit.xml.dist export-ignore\n.github export-ignore\n.gitignore export-ignore\n.gitattributes export-ignore\n.editorconfig export-ignore\ntests/test_app export-ignore\ntests/test_files export-ignore\ntests/TestCase.php export-ignore\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: dereuromark\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"npm\"\n    directory: \"/docs\"\n    schedule:\n      interval: \"weekly\"\n    ignore:\n      # vite is a transitive dep of vitepress; vitepress 1.x pins vite ^5.4.x\n      # and security fixes for vite require >= 6.4.2. Drop this ignore once\n      # vitepress 2.x ships stable (currently alpha) and we can upgrade.\n      - dependency-name: \"vite\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\njobs:\n  testsuite:\n    runs-on: ubuntu-24.04\n    strategy:\n      fail-fast: false\n      matrix:\n        php-version: ['8.2', '8.5']\n        db-type: [sqlite, mysql, pgsql]\n        prefer-lowest: ['']\n        include:\n          - php-version: '8.2'\n            db-type: 'sqlite'\n            prefer-lowest: 'prefer-lowest'\n\n    services:\n      postgres:\n        image: postgres\n        ports:\n          - 5432:5432\n        env:\n          POSTGRES_PASSWORD: postgres\n\n    steps:\n      - uses: actions/checkout@v5\n\n      - name: Setup Service\n        if: matrix.db-type == 'mysql'\n        run: |\n          sudo service mysql start\n          mysql -h 127.0.0.1 -u root -proot -e 'CREATE DATABASE cakephp;'\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php-version }}\n          extensions: mbstring, intl, pdo_${{ matrix.db-type }}\n          coverage: pcov\n\n      - name: Get composer cache directory\n        id: composercache\n        run: echo \"dir=$(composer config cache-files-dir)\" >> $GITHUB_OUTPUT\n\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: ${{ steps.composercache.outputs.dir }}\n          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}\n          restore-keys: ${{ runner.os }}-composer-\n\n      - name: Composer install\n        run: |\n          composer --version\n          if ${{ matrix.prefer-lowest == 'prefer-lowest' }}\n          then\n            composer update --prefer-lowest --prefer-stable\n            composer require --dev dereuromark/composer-prefer-lowest:dev-master\n          else\n            composer install --no-progress --prefer-dist --optimize-autoloader\n          fi\n\n      - name: Run PHPUnit\n        run: |\n          if [[ ${{ matrix.db-type }} == 'sqlite' ]]; then export DB_URL='sqlite:///:memory:'; fi\n          if [[ ${{ matrix.db-type }} == 'mysql' ]]; then export DB_URL='mysql://root:root@127.0.0.1/cakephp'; fi\n          if [[ ${{ matrix.db-type }} == 'pgsql' ]]; then export DB_URL='postgres://postgres:postgres@127.0.0.1/postgres'; fi\n          if [[ ${{ matrix.php-version }} == '8.2' ]]; then\n            vendor/bin/phpunit --coverage-clover=coverage.xml\n          else\n            vendor/bin/phpunit\n          fi\n\n      - name: Validate prefer-lowest\n        if: matrix.prefer-lowest == 'prefer-lowest'\n        run: vendor/bin/validate-prefer-lowest -m\n\n      - name: Upload coverage reports to Codecov\n        if: success() && matrix.php-version == '8.2'\n        uses: codecov/codecov-action@v5\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n\n  validation:\n    name: Coding Standard & Static Analysis\n    runs-on: ubuntu-24.04\n\n    steps:\n      - uses: actions/checkout@v5\n\n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: '8.2'\n          extensions: mbstring, intl\n          coverage: none\n\n      - name: Get composer cache directory\n        id: composercache\n        run: echo \"dir=$(composer config cache-files-dir)\" >> $GITHUB_OUTPUT\n\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: ${{ steps.composercache.outputs.dir }}\n          key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}\n          restore-keys: ${{ runner.os }}-composer-\n\n      - name: Composer Setup\n        run: composer stan-setup\n\n      - name: Run phpstan\n        run: vendor/bin/phpstan analyse --error-format=github\n\n      - name: Run phpcs\n        run: composer cs-check\n"
  },
  {
    "path": ".github/workflows/deploy-docs.yml",
    "content": "name: Deploy Documentation\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - 'docs/**'\n      - '.github/workflows/deploy-docs.yml'\n  workflow_dispatch:\n\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\nconcurrency:\n  group: pages\n  cancel-in-progress: false\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node\n        uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: npm\n          cache-dependency-path: docs/package-lock.json\n\n      - name: Setup Pages\n        uses: actions/configure-pages@v5\n\n      - name: Install dependencies\n        run: npm ci\n        working-directory: docs\n\n      - name: Build with VitePress\n        run: npm run docs:build\n        working-directory: docs\n\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          path: docs/.vitepress/dist\n\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    needs: build\n    runs-on: ubuntu-latest\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".gitignore",
    "content": "/phpunit.phar\n/.phpunit.result.cache\n/.phpunit.cache/\n/vendor/\n/tmp/\n/composer.lock\n/composer.phar\n# IDE and editor specific files #\n#################################\n/nbproject/\n/.idea/\n/.phpstorm.meta.php\n/tests/test_app/.phpstorm.meta.php/.ide-helper.meta.php\n.phpcs.cache\n\n# Docs\n/docs/node_modules/\n/docs/.vitepress/dist/\n/docs/.vitepress/cache/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Mark Sch.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "#  CakePHP IdeHelper Plugin\n\n[![CI](https://github.com/dereuromark/cakephp-ide-helper/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/dereuromark/cakephp-ide-helper/actions/workflows/ci.yml?query=branch%3Amaster)\n[![Coverage Status](https://img.shields.io/codecov/c/github/dereuromark/cakephp-ide-helper/master.svg)](https://app.codecov.io/github/dereuromark/cakephp-ide-helper/tree/master)\n[![PHPStan](https://img.shields.io/badge/PHPStan-level%208-brightgreen.svg?style=flat)](https://phpstan.org/)\n[![Latest Stable Version](https://poser.pugx.org/dereuromark/cakephp-ide-helper/v/stable.svg)](https://packagist.org/packages/dereuromark/cakephp-ide-helper)\n[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%208.2-8892BF.svg)](https://php.net/)\n[![License](https://poser.pugx.org/dereuromark/cakephp-ide-helper/license.svg)](LICENSE)\n[![Total Downloads](https://poser.pugx.org/dereuromark/cakephp-ide-helper/d/total.svg)](https://packagist.org/packages/dereuromark/cakephp-ide-helper)\n\nIdeHelper plugin for CakePHP applications.\n\n> Boost your productivity. Avoid mistakes.\n\nThis branch is for use with **CakePHP 5.1+**. For details see [version map](https://github.com/dereuromark/cakephp-ide-helper/wiki#cakephp-version-map).\n\n## Features\n\nThe main idea is to improve IDE compatibility and use annotations to make the IDE understand the\n\"magic\" of CakePHP, so you can click through the class methods and object chains as well as spot obvious issues and mistakes easier. The IDE will usually mark problematic code yellow (missing, wrong method etc).\n\nThis also improves compatibility with tools like [PHPStan](https://github.com/phpstan/phpstan).\nThose can then follow the code easier and provide more valuable help.\n\n- Add annotations to existing code (e.g. when upgrading an application) just like baking would to new code.\n- Can run multiple times without adding the annotations again.\n- It can also replace or remove outdated annotations.\n- Works with your application as well as any loaded plugin.\n- CI check support, hook it up just like the coding standards check.\n- Set up as watcher and have live in-real-time annotations added/updated.\n\nSupports annotations for:\n- Models (Tables and Entities)\n- Controllers (including prefixes like `Admin`) and Components\n- View (AppView) and Helpers\n- Templates (`.php` PHP template files including elements)\n- Commands and Tasks\n- ... and more\n\n![Screenshot](docs/public/screenshot.jpg)\n\nSupports code completion help for:\n- Behaviors (property access on the BehaviorRegistry)\n\nSupports IDE autocomplete/typehinting of (magic)strings as well as return types/values for:\n- Plugins, Components, Behaviors, Helpers, Mailers\n- Associations, Validation\n- I18n Translation, Cache\n- Elements and layouts\n- Tables and their fields\n- Route paths, Request/ENV, Connection\n- ... and more (using PhpStorm meta file)\n\nSupports better IDE usage with Illuminator tasks to enhance existing code:\n- EntityFieldTask adds all entity fields as class constants for easier usage in IDEs\n\n### IDE support\nThis plugin is supposed to work with ANY IDE that supports annotations and code completion.\nIDEs tested so far for 100% compatibility:\n- **[PhpStorm](https://github.com/dereuromark/cakephp-ide-helper/wiki/PHPStorm)** (incl. meta file generator)\n- IntelliJ\n- Atom\n- **[VS Code](https://github.com/dereuromark/cakephp-ide-helper/wiki/Visual-Studio-Code)**\n- ... [Report or PR your IDE of choice here to confirm its full compatibility]\n\nSee [Wiki](https://github.com/dereuromark/cakephp-ide-helper/wiki) for details and tips/settings.\n\n### Plugin with Annotator tasks\n- [CakephpFixtureFactories](https://github.com/dereuromark/cakephp-fixture-factories) for Factory class usage through `@extends`.\n\n### Plugins with meta file generator tasks\nThe following plugins use this plugin to improve IDE compatibility around factory and magic string usage:\n- [Migrations](https://github.com/cakephp/migrations) for migration file writing (included in IdeHelper directly).\n- [Queue](https://github.com/dereuromark/cakephp-queue) for `QueuedJobsTable::createJob()` usage.\n- [Burzum/CakeServiceLayer](https://github.com/burzum/cakephp-service-layer) for `loadService()` usage.\n- ... (add yours here)\n\n### Plugins with Illuminator tasks\n- [StateMachine](https://github.com/spryker/cakephp-statemachine) for syncing states from XML into PHP.\n- ... (add yours here)\n\n### More\nMore collections of useful tasks can be found in the [IdeHelperExtra plugin](https://github.com/dereuromark/cakephp-ide-helper-extra).\n\n### Install, Setup, Usage\nSee the **[Documentation](https://dereuromark.github.io/cakephp-ide-helper/)** for details.\n\n\n### Powered by\n[![PhpStorm logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/PhpStorm.svg)](https://jb.gg/OpenSourceSupport)\n\nThey are sponsoring my IDE for my FOSS work on this repository and beyond.\n"
  },
  {
    "path": "annotate-watcher.cjs",
    "content": "// watcher.js\nconst chokidar = require('chokidar');\nconst { exec } = require('child_process');\n\n// Helper to parse CLI arguments\nfunction getPathsFromArgs() {\n\tconst arg = process.argv.find(arg => arg.startsWith('--path='));\n\tif (!arg) return ['src/', 'templates/']; // default path\n\tconst value = arg.split('=')[1];\n\treturn value.split(',').map(p => p.trim()).filter(Boolean);\n}\n\nconst watchPaths = getPathsFromArgs();\nconsole.log(`🔍 Watching: ${watchPaths.join(', ')}`);\n\n// Initialize watcher\nchokidar.watch(watchPaths, {\n\tignored: /(^|[\\/\\\\])\\../, // ignore dotfiles\n\tpersistent: true,\n})\n\t.on('change', path => {\n\t\tif (!path.endsWith('.php')) return; // skip non-PHP files\n\n\t\tconsole.log(`📝 File changed: ${path}`);\n\n\t\t// Run CakePHP command\n\t\texec('bin/cake annotate all --file ' + `${path}`, (error, stdout, stderr) => {\n\t\t\tif (error) {\n\t\t\t\tconsole.error(`❌ Error executing CakePHP command: ${error.message}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (stderr) {\n\t\t\t\tconsole.error(`⚠️ STDERR: ${stderr}`);\n\t\t\t}\n\t\t\tconsole.log(`✅ Output:\\n${stdout}`);\n\t\t});\n\t});\n"
  },
  {
    "path": "composer.json",
    "content": "{\n\t\"name\": \"dereuromark/cakephp-ide-helper\",\n\t\"description\": \"CakePHP IdeHelper Plugin to improve auto-completion\",\n\t\"license\": \"MIT\",\n\t\"type\": \"cakephp-plugin\",\n\t\"keywords\": [\n\t\t\"cakephp\",\n\t\t\"IDE\",\n\t\t\"autocomplete\",\n\t\t\"annotations\",\n\t\t\"phpstorm\",\n\t\t\"phpdoc\",\n\t\t\"dev\",\n\t\t\"cli\"\n\t],\n\t\"authors\": [\n\t\t{\n\t\t\t\"name\": \"Mark Scherer\",\n\t\t\t\"homepage\": \"https://www.dereuromark.de\",\n\t\t\t\"role\": \"Maintainer\"\n\t\t},\n\t\t{\n\t\t\t\"name\": \"Other contributors\",\n\t\t\t\"homepage\": \"https://github.com/dereuromark/cakephp-ide-helper/graphs/contributors\",\n\t\t\t\"role\": \"Developer\"\n\t\t}\n\t],\n\t\"homepage\": \"https://github.com/dereuromark/cakephp-ide-helper/\",\n\t\"support\": {\n\t\t\"issues\": \"https://github.com/dereuromark/cakephp-ide-helper/issues\",\n\t\t\"source\": \"https://github.com/dereuromark/cakephp-ide-helper/\"\n\t},\n\t\"require\": {\n\t\t\"php\": \">=8.2\",\n\t\t\"cakephp/bake\": \"^3.2.0\",\n\t\t\"cakephp/cakephp\": \"^5.1.5\",\n\t\t\"nikic/php-parser\": \"^5.7\",\n\t\t\"phpstan/phpdoc-parser\": \"^2.1.0\",\n\t\t\"sebastian/diff\": \"^6.0 || ^7.0 || ^8.0\",\n\t\t\"squizlabs/php_codesniffer\": \"^3.13 || ^4.0\"\n\t},\n\t\"require-dev\": {\n\t\t\"dereuromark/cakephp-shim\": \"^3.3.0\",\n\t\t\"cakephp/migrations\": \"^4.5.1 || ^5.0\",\n\t\t\"cakephp/plugin-installer\": \"^2.0.1\",\n\t\t\"fig-r/psr2r-sniffer\": \"@stable\",\n\t\t\"phpunit/phpunit\": \"^11.5 || ^12.1 || ^13.0\",\n\t\t\"phpstan/phpstan\": \"^2.0\"\n\t},\n\t\"minimum-stability\": \"stable\",\n\t\"prefer-stable\": true,\n\t\"autoload\": {\n\t\t\"psr-4\": {\n\t\t\t\"IdeHelper\\\\\": \"src/\",\n\t\t\t\"IdeHelper\\\\Test\\\\Fixture\\\\\": \"tests/Fixture/\"\n\t\t}\n\t},\n\t\"autoload-dev\": {\n\t\t\"psr-4\": {\n\t\t\t\"Awesome\\\\\": \"tests/test_app/plugins/Awesome/src/\",\n\t\t\t\"Cake\\\\Test\\\\\": \"vendor/cakephp/cakephp/tests/\",\n\t\t\t\"Controllers\\\\\": \"tests/test_app/plugins/Controllers/src/\",\n\t\t\t\"IdeHelper\\\\PHPStan\\\\\": \"tests/PHPStan/\",\n\t\t\t\"IdeHelper\\\\Test\\\\\": \"tests/\",\n\t\t\t\"MyNamespace\\\\MyPlugin\\\\\": \"tests/test_app/plugins/MyNamespace/MyPlugin/src/\",\n\t\t\t\"MyNamespace\\\\MyPlugin\\\\Test\\\\Fixture\\\\\": \"tests/test_app/plugins/MyNamespace/MyPlugin/tests/Fixture/\",\n\t\t\t\"Relations\\\\\": \"tests/test_app/plugins/Relations/src/\",\n\t\t\t\"TestApp\\\\\": \"tests/test_app/src/\"\n\t\t}\n\t},\n\t\"config\": {\n\t\t\"allow-plugins\": {\n\t\t\t\"cakephp/plugin-installer\": true,\n\t\t\t\"dealerdirect/phpcodesniffer-composer-installer\": true\n\t\t}\n\t},\n\t\"scripts\": {\n\t\t\"cs-check\": \"phpcs --colors --parallel=16\",\n\t\t\"cs-fix\": \"phpcbf --colors --parallel=16\",\n\t\t\"lowest\": \"validate-prefer-lowest\",\n\t\t\"lowest-setup\": \"composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction && cp composer.json composer.backup && composer require --dev dereuromark/composer-prefer-lowest && mv composer.backup composer.json\",\n\t\t\"lowest-setup-debug\": \"cp composer.json composer.backup && composer require --dev phpunit/phpunit:^11.5 -W && composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction && composer require --dev dereuromark/composer-prefer-lowest && mv composer.backup composer.json\",\n\t\t\"stan\": \"phpstan analyze\",\n\t\t\"stan-setup\": \"cp composer.json composer.backup && composer require --dev phpstan/phpstan:^2.0.0 && mv composer.backup composer.json\",\n\t\t\"stan-tests\": \"phpstan analyze -c tests/phpstan.neon\",\n\t\t\"test\": \"phpunit\",\n\t\t\"test-coverage\": \"phpunit --log-junit tmp/coverage/unitreport.xml --coverage-html tmp/coverage --coverage-clover tmp/coverage/coverage.xml\"\n\t}\n}\n"
  },
  {
    "path": "config/app.example.php",
    "content": "<?php\n\nreturn [\n\t// Copy the following over to your project one in ROOT/config/\n\t'IdeHelper' => [\n\t\t// Additional plugins that are not loaded, but should be included, use `-` prefix to exclude\n\t\t'plugins' => [],\n\t\t// Controller prefixes to check for\n\t\t'prefixes' => [\n\t\t\t'Admin',\n\t\t],\n\t\t// Template paths to skip\n\t\t'skipTemplatePaths' => [\n\t\t\t'/templates/Bake/',\n\t\t],\n\t\t'templateExtensions' => [\n\t\t\t'php',\n\t\t],\n\t\t// How behaviors are annotated into tables: ['mixin', 'extends', true, false, null]\n\t\t'tableBehaviors' => null, // null auto-detects based CakePHP version (>= 5.2.2 for extends) and always adds mixins.\n\t\t'arrayAsGenerics' => false, // Enable to have modern generics syntax (recommended) in doc blocks\n\t\t'objectAsGenerics' => false, // Enable to have modern generics syntax (recommended) in doc blocks\n\t\t'assocsAsGenerics' => false, // Enable to have modern generics syntax (NOT recommended yet) in doc blocks\n\t\t'genericsInParam' => false, // true for basic generics, 'detailed' for fully detailed types (array<string, mixed>, ResultSetInterface<int, TEntity>, ...)\n\t\t'concreteEntitiesInParam' => false, // Enable to have specific entities in generated method param doc blocks (NOT recommended)\n\t\t'tableEntityQuery' => false, // Enable to annotate `Table::find()` as returning `SelectQuery<TEntity>` for IDEs\n\t\t// Set to `false` to disable, or string if you have a custom FQCN to be used\n\t\t'templateCollectionObject' => true,\n\t\t// Set to `false` to disable, defaults to `mixed` if enabled, you can also pass callable for logic\n\t\t'autoCollect' => true,\n\t\t// Can be strings or `/regex/` (e.g. `'/^\\_.+$/i'` for underscore prefixed variables)\n\t\t'autoCollectBlacklist' => [],\n\t\t'preferLinkOverUsesInTests' => true, // Prefer `@link` annotations over `@uses` in test files, prevents PHPUnit/Rector to replace them with attributes.\n\t\t// Custom Entity field type mapping\n\t\t'typeMap' => [],\n\t\t// Default View class to use\n\t\t'viewClass' => null,\n\t\t// Plugins to include for View annotations\n\t\t'includedPlugins' => [],\n\t\t// Always add annotations/meta even if not yet needed\n\t\t'preemptive' => false,\n\t\t// Annotator task customization\n\t\t'annotators' => [],\n\t\t// For meta file generator\n\t\t'generatorTasks' => [],\n\t\t// A regex pattern - for Migrations plugin DatabaseTableTask generator task\n\t\t'ignoreDatabaseTables' => null,\n\t\t// A list of tables - for Migrations plugin DatabaseTableTask generator task\n\t\t'skipDatabaseTables' => null,\n\t\t// For Illuminator tasks\n\t\t'illuminatorTasks' => [],\n\t\t'illuminatorIndentation' => \"\\t\",\n\t\t// For code completion file generator\n\t\t'codeCompletionTasks' => [],\n\t\t// If a custom directory should be used, defaults to TMP otherwise\n\t\t'codeCompletionPath' => null,\n\t\t'codeCompletionReturnType' => null, // Auto-detect based on controller/component, set to true/false to force one mode.\n\t],\n];\n"
  },
  {
    "path": "docs/.vitepress/config.ts",
    "content": "import { defineConfig } from 'vitepress'\n\nexport default defineConfig({\n  title: 'cakephp-ide-helper',\n  description: 'Annotations, meta files, code-completion, and Illuminator tasks for CakePHP — boost IDE and static analyzer support.',\n  base: '/cakephp-ide-helper/',\n  head: [\n    ['link', { rel: 'icon', href: '/cakephp-ide-helper/favicon.svg', type: 'image/svg+xml' }],\n  ],\n  themeConfig: {\n    logo: '/logo.svg',\n    nav: [\n      { text: 'Guide', link: '/guide/', activeMatch: '/guide/' },\n      { text: 'Annotations', link: '/annotations/', activeMatch: '/annotations/' },\n      { text: 'Code Completion', link: '/code-completion/', activeMatch: '/code-completion/' },\n      { text: 'Generator', link: '/generator/', activeMatch: '/generator/' },\n      { text: 'Illuminator', link: '/illuminator/', activeMatch: '/illuminator/' },\n      { text: 'Reference', link: '/reference/configuration', activeMatch: '/reference/' },\n      {\n        text: 'Links',\n        items: [\n          { text: 'GitHub', link: 'https://github.com/dereuromark/cakephp-ide-helper' },\n          { text: 'Packagist', link: 'https://packagist.org/packages/dereuromark/cakephp-ide-helper' },\n          { text: 'Issues', link: 'https://github.com/dereuromark/cakephp-ide-helper/issues' },\n          { text: 'Wiki', link: 'https://github.com/dereuromark/cakephp-ide-helper/wiki' },\n          { text: 'IdeHelperExtra', link: 'https://github.com/dereuromark/cakephp-ide-helper-extra' },\n        ],\n      },\n    ],\n    sidebar: {\n      '/guide/': [\n        {\n          text: 'Guide',\n          items: [\n            { text: 'Introduction', link: '/guide/' },\n            { text: 'Installation', link: '/guide/installation' },\n            { text: 'Usage', link: '/guide/usage' },\n            { text: 'IDE Support', link: '/guide/ide-support' },\n            { text: 'Migrating from 4.x', link: '/guide/migration' },\n          ],\n        },\n      ],\n      '/annotations/': [\n        {\n          text: 'Annotations',\n          items: [\n            { text: 'Overview', link: '/annotations/' },\n            { text: 'Controllers', link: '/annotations/controllers' },\n            { text: 'Models', link: '/annotations/models' },\n            { text: 'View, Components, Helpers', link: '/annotations/view' },\n            { text: 'Templates', link: '/annotations/templates' },\n            { text: 'Commands and Routes', link: '/annotations/commands' },\n            { text: 'Classes', link: '/annotations/classes' },\n            { text: 'Callbacks', link: '/annotations/callbacks' },\n            { text: 'Operations', link: '/annotations/operations' },\n            { text: 'Custom Class Annotators', link: '/annotations/custom-class-annotator' },\n          ],\n        },\n      ],\n      '/code-completion/': [\n        {\n          text: 'Code Completion',\n          items: [\n            { text: 'Overview', link: '/code-completion/' },\n          ],\n        },\n      ],\n      '/generator/': [\n        {\n          text: 'Generator',\n          items: [\n            { text: 'Overview', link: '/generator/' },\n            { text: 'Available Tasks', link: '/generator/tasks' },\n            { text: 'Custom Tasks and Directives', link: '/generator/custom-tasks' },\n            { text: 'Operations', link: '/generator/operations' },\n          ],\n        },\n      ],\n      '/illuminator/': [\n        {\n          text: 'Illuminator',\n          items: [\n            { text: 'Overview', link: '/illuminator/' },\n          ],\n        },\n      ],\n      '/reference/': [\n        {\n          text: 'Reference',\n          items: [\n            { text: 'Configuration', link: '/reference/configuration' },\n            { text: 'Contributing', link: '/reference/contributing' },\n          ],\n        },\n      ],\n    },\n    socialLinks: [\n      { icon: 'github', link: 'https://github.com/dereuromark/cakephp-ide-helper' },\n    ],\n    search: {\n      provider: 'local',\n    },\n    editLink: {\n      pattern: 'https://github.com/dereuromark/cakephp-ide-helper/edit/master/docs/:path',\n      text: 'Edit this page on GitHub',\n    },\n    footer: {\n      message: 'Released under the MIT License.',\n      copyright: 'Copyright Mark Scherer',\n    },\n  },\n})\n"
  },
  {
    "path": "docs/.vitepress/theme/custom.css",
    "content": ":root {\n  --vp-c-brand-1: #6d28d9;\n  --vp-c-brand-2: #7c3aed;\n  --vp-c-brand-3: #a78bfa;\n  --vp-c-brand-soft: rgba(167, 139, 250, 0.14);\n\n  --vp-home-hero-name-color: transparent;\n  --vp-home-hero-name-background: linear-gradient(135deg, #6d28d9 0%, #0284c7 100%);\n  --vp-home-hero-image-background-image: linear-gradient(135deg, #6d28d9 0%, #0284c7 100%);\n  --vp-home-hero-image-filter: blur(42px);\n}\n\n.dark {\n  --vp-c-brand-1: #a78bfa;\n  --vp-c-brand-2: #8b5cf6;\n  --vp-c-brand-3: #7c3aed;\n}\n\n.vp-doc table code,\n.vp-doc p code,\n.vp-doc li code {\n  white-space: nowrap;\n}\n\n.vp-doc .custom-block.tip {\n  border-color: var(--vp-c-brand-1);\n}\n"
  },
  {
    "path": "docs/.vitepress/theme/index.ts",
    "content": "import DefaultTheme from 'vitepress/theme'\nimport './custom.css'\n\nexport default DefaultTheme\n"
  },
  {
    "path": "docs/annotations/callbacks.md",
    "content": "# Callbacks and CallbackAnnotationTasks\n\nThis is a separate annotations tool that focuses on **methods** and their doc\nblocks instead of classes. By default it ships with:\n\n- `TableCallbackAnnotatorTask`\n\n## Table Callback Annotations\n\nBehaviors and generic code use the following signature:\n\n```php\n/**\n * @param \\Cake\\Event\\EventInterface $event Event\n * @param \\Cake\\Datasource\\EntityInterface $entity Entity\n * @param \\ArrayObject $options Options\n * @return void\n */\npublic function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void\n```\n\nAs long as you only use methods and attributes of the `EntityInterface`\ncontract, this is fine.\n\nBut in specific Table-class code, you usually also access the entity's\nconcrete properties. There the type hint is somewhat a lie. To please the IDE\nand tooling like PHPStan we can at least fix up the doc block — and that is\nwhat this task does, declaring the `Post` entity to be available and used\ninside.\n\nInside the concrete `PostsTable` after running the `callbacks` command:\n\n```php\n/**\n * @param \\Cake\\Event\\EventInterface $event Event\n * @param \\App\\Model\\Entity\\Post $entity Entity\n * @param \\ArrayObject $options Options\n * @return void\n */\npublic function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void\n```\n\n## Entity Virtual Field Setter/Getter Annotations\n\nA virtual field will be \"linked\" to the property it handles:\n\n```php\n/**\n * @see \\App\\Model\\Entity\\MyEntity::$expected_type\n *\n * @return int|null\n */\nprotected function _getExpectedType(): ?int\n```\n\nThis way you can quick-jump from the property to the getter and vice versa\nwithin your IDE.\n\n## Custom Tasks\n\nCreate your own task class:\n\n```php\nnamespace App\\Annotator\\CallbackAnnotatorTask;\n\nuse IdeHelper\\Annotator\\CallbackAnnotatorTask\\AbstractCallbackAnnotatorTask;\nuse IdeHelper\\Annotator\\CallbackAnnotatorTask\\CallbackAnnotatorTaskInterface;\n\nclass MyCallbackAnnotatorTask extends AbstractCallbackAnnotatorTask implements CallbackAnnotatorTaskInterface {\n\n    /**\n     * @param string $path\n     * @return bool\n     */\n    public function shouldRun(string $path): bool {\n        // ...\n    }\n\n    /**\n     * @param string $path\n     * @return bool\n     */\n    public function annotate(string $path): bool {\n        // ...\n    }\n\n}\n```\n\nThen add it to the config:\n\n```php\n'IdeHelper' => [\n    'CallbackAnnotatorTasks' => [\n        'MyCallbackAnnotatorTask' => \\App\\Annotator\\CallbackAnnotatorTask\\MyCallbackAnnotatorTask::class,\n    ],\n],\n```\n\nThe key `'MyCallbackAnnotatorTask'` can be any string.\n\nReplacing existing tasks works the same way as for classes: use the native\nclass name as key, your replacement as value, or `null` to disable.\n"
  },
  {
    "path": "docs/annotations/classes.md",
    "content": "# Classes and ClassAnnotationTasks\n\nIn order to run certain \"fixers\" over all classes, class annotations and their\ntasks are available. Out of the box the following tasks are run.\n\n## ModelAware\n\nAny `use ModelAwareTrait` together with `$this->loadModel(...)` calls will add\nthe required annotation on top of the class.\n\n## `Form::execute()`\n\nAdds a convenience inline annotation so you can quickly jump to the actual\nbusiness logic:\n\n```php\nuse App\\Form\\ReleaseForm;\n\n$releaseForm = new ReleaseForm();\n\n/** @uses \\App\\Form\\ReleaseForm::_execute() */\n$releaseForm->execute($data);\n```\n\n## `Mailer::send()`\n\nAdds a convenience inline annotation so you can quickly jump to the actual\nbusiness logic:\n\n```php\nuse App\\Mailer\\NotificationMailer;\n// or\n$notificationMailer = $this->getMailer('Notification');\n\n/** @uses \\App\\Mailer\\NotificationMailer::notify() */\n$notificationMailer->send('notify', [$user, $details]);\n```\n\n## Test\n\nTest classes of specific types can be annotated with the corresponding class\nthey test. This is mainly useful for the following types, which are invoked\nindirectly via the integration test harness:\n\n- Controller\n- Command\n\nThe `@link` statement helps to quick-jump to the class if needed. If your test\nclass already has a `#[UsesClass(...)]` attribute, no annotation will be\nadded.\n\n::: tip Use `@uses` instead of `@link`\nSet the `IdeHelper.preferLinkOverUsesInTests` config key to `false` to use\n`@uses` instead of `@link`.\n:::\n\n## Custom Tasks\n\nCreate your own task class:\n\n```php\nnamespace App\\Annotator\\ClassAnnotatorTask;\n\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\AbstractClassAnnotatorTask;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface;\n\nclass MyClassAnnotatorTask extends AbstractClassAnnotatorTask implements ClassAnnotatorTaskInterface {\n\n    /**\n     * @param string $path\n     * @param string $content\n     * @return bool\n     */\n    public function shouldRun(string $path, string $content): bool {\n        // ...\n    }\n\n    /**\n     * @param string $path\n     * @return bool\n     */\n    public function annotate(string $path): bool {\n        // ...\n    }\n\n}\n```\n\nThen add it to the config:\n\n```php\n'IdeHelper' => [\n    'classAnnotatorTasks' => [\n        'MyClassAnnotatorTask' => \\App\\Annotator\\ClassAnnotatorTask\\MyClassAnnotatorTask::class,\n    ],\n],\n```\n\nThe key `'MyClassAnnotatorTask'` can be any string.\n\nFor a fully worked PHP-AST example, see [Custom Class Annotators](./custom-class-annotator).\n\n## Targeting Custom Directories\n\nBy default `bin/cake annotate classes` walks `src/` (app + plugin classpaths)\nand `tests/TestCase/` (when `TestClassAnnotatorTask` is registered). A custom\ntask whose subjects live elsewhere — for example test-fixture factories under\n`tests/Factory/`, scenario classes, or generated stubs — can opt into having\nthose directories walked by also implementing\n`PathAwareClassAnnotatorTaskInterface`:\n\n```php\nnamespace App\\Annotator\\ClassAnnotatorTask;\n\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\AbstractClassAnnotatorTask;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\PathAwareClassAnnotatorTaskInterface;\n\nclass MyClassAnnotatorTask extends AbstractClassAnnotatorTask implements PathAwareClassAnnotatorTaskInterface {\n\n    /**\n     * @return array<string>\n     */\n    public static function scanPaths(): array {\n        return ['tests/Factory/'];\n    }\n\n    public function shouldRun(string $path, string $content): bool { /* ... */ }\n    public function annotate(string $path): bool { /* ... */ }\n\n}\n```\n\nPaths are project-root relative for app context, plugin-root relative when\nrun with `-p`. They are walked recursively. Paths that do not exist on disk\nare silently skipped, and a path declared by multiple tasks is walked only\nonce.\n\n::: tip Path convention\nReturn paths with forward slashes and a trailing slash (e.g.\n`'tests/Factory/'`), independent of OS. The command normalizes to the\nOS-native separator before walking, so the dedup key stays stable across\nWindows / \\*nix and across tasks that disagree on style.\n:::\n\nThe interface is optional and additive — existing tasks that do not implement\nit behave unchanged. The feature is opt-in: a path-aware task is only\nconsulted when it is registered in `IdeHelper.classAnnotatorTasks`.\n\n## Replacing Native Tasks\n\nUsing associative arrays you can swap any native task with your own\nimplementation:\n\n```php\n'IdeHelper' => [\n    'classAnnotatorTasks' => [\n        \\IdeHelper\\Annotator\\ClassAnnotatorTask\\ModelAwareTask::class => \\App\\Annotator\\ClassAnnotatorTask\\MyEnhancedModelAwareTask::class,\n    ],\n],\n```\n\nThe native class name is the key, your replacement the value. Setting the\nvalue to `null` disables a native task entirely.\n"
  },
  {
    "path": "docs/annotations/commands.md",
    "content": "# Commands and Routes\n\n## Commands\n\nCommands should annotate their primary model as well as all manually loaded\nmodels.\n\n```bash\nbin/cake annotate commands\n```\n\n```php\n    /**\n     * @var string|null\n     */\n    protected ?string $defaultTable = 'Cars';\n\n    /**\n     * @return int\n     */\n    public function execute(Arguments $args, ConsoleIo $io): int {\n        $this->fetchTable('MyPlugin.Wheels');\n\n        return static::CODE_SUCCESS;\n    }\n```\n\nresults in:\n\n```php\n/**\n * @property \\MyPlugin\\Model\\Table\\WheelsTable $Wheels\n * @property \\App\\Model\\Table\\CarsTable $Cars\n */\n```\n\n## Routes\n\nRoute files in 4.x are no longer required to be static. The\n`config/routes.php` file gets the following annotation so the `$routes` object\nis type-hinted for editors and analyzers:\n\n```php\n/**\n * @var \\Cake\\Routing\\RouteBuilder $routes\n */\n```\n"
  },
  {
    "path": "docs/annotations/controllers.md",
    "content": "# Controllers\n\nAll controllers should at least annotate their primary model. They should also\nannotate the other loaded models as well as the loaded components.\n\n```bash\nbin/cake annotate controllers\n```\n\n## Primary Model via Convention\n\n```php\n<?php\nnamespace App\\Controller;\n\nclass ApplesController extends AppController {\n}\n```\n\nbecomes:\n\n```php\n<?php\nnamespace App\\Controller;\n\n/**\n * @property \\App\\Model\\Table\\ApplesTable $Apples\n */\nclass ApplesController extends AppController {\n}\n```\n\nYou get autocompletion on any `$this->Apples->...()` usage in your controllers.\n\nUse `-p PluginName` to annotate inside a plugin.\n\n::: tip Plugin wildcards\nUse `*` to refer to a group of plugins, e.g. `-p SomePrefix/*` for everything\nunder your own `plugins/` directory. You can also use `all` for all app\nplugins.\n\nFor more than one plugin the command will not run into `vendor/` plugins, to\navoid accidental modification there.\n:::\n\n## Primary Model via `$defaultTable`\n\nWhen defining `$defaultTable` it will be used instead:\n\n```php\n<?php\nnamespace App\\Controller;\n\n/**\n * @property \\App\\Model\\Table\\MyApplesTable $MyApples\n */\nclass ApplesController extends AppController {\n\n    protected ?string $defaultTable = 'MyApples';\n\n}\n```\n\n## Custom Prefixes\n\nBy default, the annotator supports any prefix for your controllers (as a\nsubfolder). Using the Configure key `'IdeHelper.prefixes'` you can configure a\nprefix whitelist.\n"
  },
  {
    "path": "docs/annotations/custom-class-annotator.md",
    "content": "# Custom Class Annotators\n\nWorked examples of writing custom class annotators in your CakePHP application.\n\n## `getByUuid()` — Concrete Entity Inline Annotation\n\nImagine you have a method `getByUuid()` for all your tables to look up records\nby their UUID instead of the AIID. The returned result would be \"known\" by\nthe IDE / PHPStan as `EntityInterface`.\n\nYou can create a custom annotator to add a concrete entity annotation\nautomatically. It adds an inline `@var` annotation above each `getByUuid()`\nmethod assignment.\n\nThe example below uses PHP-AST to parse the class files and find the relevant\nmethod calls. This is more exact than using a regex or PHPCS tokenizing, with\nfewer false positives.\n\nIt then uses the existing IdeHelper code modifier that leverages PHPCS\ntokenizing to add the inline annotation to the class file via\n`annotateInlineContent()`.\n\n```php\n<?php\n\nnamespace App\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\ORM\\TableRegistry;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\VariableAnnotation;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\AbstractClassAnnotatorTask;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface;\nuse PhpParser\\Node;\nuse PhpParser\\Node\\Expr\\Assign;\nuse PhpParser\\Node\\Expr\\MethodCall;\nuse PhpParser\\Node\\Expr\\PropertyFetch;\nuse PhpParser\\Node\\Expr\\Variable;\nuse PhpParser\\Node\\Identifier;\nuse PhpParser\\NodeTraverser;\nuse PhpParser\\NodeVisitorAbstract;\nuse PhpParser\\ParserFactory;\n\n/**\n * Usage of getByUuid() should have inline annotations added.\n */\nclass TableGetAnnotatorTask extends AbstractClassAnnotatorTask implements ClassAnnotatorTaskInterface\n{\n    /**\n     * @param string $path\n     * @param string $content\n     *\n     * @return bool\n     */\n    public function shouldRun(string $path, string $content): bool\n    {\n        if (!str_contains($path, DS . 'src' . DS)) {\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * @param string $path\n     *\n     * @return bool\n     */\n    public function annotate(string $path): bool\n    {\n        $found = $this->findGetByUuidCalls();\n\n        $result = true;\n        foreach ($found as $row) {\n            $annotations = [\n                AnnotationFactory::createOrFail(\n                    VariableAnnotation::TAG,\n                    $row['entity'],\n                    $row['var'],\n                ),\n            ];\n            $result &= $this->annotateInlineContent($path, $this->content, $annotations, $row['line']);\n        }\n\n        return $result;\n    }\n\n    protected function findGetByUuidCalls(): array\n    {\n        $parser = (new ParserFactory())->createForNewestSupportedVersion();\n        $ast = $parser->parse($this->content);\n\n        $found = [];\n\n        $traverser = new NodeTraverser();\n\n        $traverser->addVisitor(new class ($found) extends NodeVisitorAbstract {\n            protected array $found;\n\n            /**\n             * @param array $found\n             */\n            public function __construct(array &$found)\n            {\n                $this->found = &$found;\n            }\n\n            /**\n             * @param \\PhpParser\\Node $node\n             *\n             * @return null\n             */\n            public function enterNode(Node $node): null\n            {\n                if (\n                    $node instanceof Assign &&\n                    $node->expr instanceof MethodCall &&\n                    $node->expr->name instanceof Identifier &&\n                    $node->expr->name->toString() === 'getByUuid'\n                ) {\n                    $varName = $node->var instanceof Variable ? $node->var->name : null;\n\n                    $caller = $node->expr->var;\n                    if (\n                        $caller instanceof PropertyFetch &&\n                        $caller->var instanceof Variable &&\n                        $caller->var->name === 'this' &&\n                        $caller->name instanceof Identifier\n                    ) {\n                        $modelName = $caller->name->toString();\n                        $entityClass = TableRegistry::getTableLocator()->get($modelName)->getEntityClass();\n\n                        $this->found[] = [\n                            'line' => $node->getStartLine(),\n                            'var' => '$' . $varName,\n                            'entity' => '\\\\' . $entityClass,\n                        ];\n                    }\n                }\n\n                return null;\n            }\n        });\n\n        $traverser->traverse($ast);\n\n        return $found;\n    }\n}\n```\n"
  },
  {
    "path": "docs/annotations/index.md",
    "content": "# Annotations Updater\n\nThe Annotator keeps doc blocks and annotations in sync with your code without\nmodifying functional code.\n\nNote that freshly [baking](https://github.com/cakephp/bake) your code produces\nsimilar results, but most projects already have existing code where re-baking\nis not an option. The annotator also keeps manually added or modified code\nannotated.\n\n## Important Options to Start With\n\nThe following are defined under the `IdeHelper` key in `app.php`:\n\n- `arrayAsGenerics`: Set to `true` to use modern generics syntax (`array<type>`\n  instead of legacy `type[]`).\n\nSee the [Configuration reference](/reference/configuration) for the full list.\n\n## Per-Class-Type Reference\n\nEach class type has its own page covering the dedicated subcommand and the\noptions that apply only to that type:\n\n- [Controllers](./controllers) — `bin/cake annotate controllers`\n- [Models](./models) — `bin/cake annotate models` (Tables and Entities)\n- [View, Components, Helpers](./view) — `annotate view`, `annotate components`, `annotate helpers`\n- [Templates](./templates) — `bin/cake annotate templates`\n- [Commands and Routes](./commands) — `bin/cake annotate commands`\n- [Classes](./classes) — class-level annotators (`ModelAware`, `Form::execute()`, `Mailer::send()`, `Test`, custom)\n- [Callbacks](./callbacks) — method-level annotators (`TableCallbackAnnotatorTask`, custom)\n\n## Cross-Cutting\n\n- [Operations](./operations) — running all commands, dry-run, filters, removal, file watcher, CI\n- [Custom Class Annotators](./custom-class-annotator) — full PHP-AST example\n\n## Replacing Native Tasks\n\nUsing associative arrays you can swap any native task with your own\nimplementation:\n\n```php\n'IdeHelper' => [\n    'annotators' => [\n        \\IdeHelper\\Annotator\\EntityAnnotator::class => \\App\\Annotator\\MyEnhancedEntityAnnotator::class,\n    ],\n],\n```\n\nThe native class name is the key, your replacement the value. Setting the\nvalue to `null` disables a native task entirely.\n"
  },
  {
    "path": "docs/annotations/models.md",
    "content": "# Models\n\nAnnotate Tables and their Entities:\n\n```bash\nbin/cake annotate models\n```\n\n## Tables\n\nTables annotate their entity-related methods, their relations, and behavior\nmixins.\n\nA `LocationsTable` class would gain the following doc-block annotations if not\nalready present:\n\n```php\n/**\n * @method \\App\\Model\\Entity\\Location newEmptyEntity()\n * @method \\App\\Model\\Entity\\Location newEntity(array $data, array $options = [])\n * @method array<\\App\\Model\\Entity\\Location> newEntities(array $data, array $options = [])\n * @method \\App\\Model\\Entity\\Location get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\Cake\\ORM\\Query\\SelectQuery<\\App\\Model\\Entity\\Location> find(string $type = 'all', mixed ...$args)\n * @method \\App\\Model\\Entity\\Location|false save(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\App\\Model\\Entity\\Location saveOrFail(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\App\\Model\\Entity\\Location patchEntity(\\Cake\\Datasource\\EntityInterface $entity, array $data, array $options = [])\n * @method array<\\App\\Model\\Entity\\Location> patchEntities(iterable $entities, array $data, array $options = [])\n * @method \\App\\Model\\Entity\\Location findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, ?callable $callback = null, array $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\App\\Model\\Entity\\Location>|false saveMany(iterable $entities, array $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\App\\Model\\Entity\\Location> saveManyOrFail(iterable $entities, array $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\App\\Model\\Entity\\Location>|false deleteMany(iterable $entities, array $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\App\\Model\\Entity\\Location> deleteManyOrFail(iterable $entities, array $options = [])\n *\n * @property \\Cake\\ORM\\Association\\HasMany<\\App\\Model\\Table\\ImagesTable> $Images\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\App\\Model\\Table\\UsersTable> $Users\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n */\n```\n\n### Entity-aware `find()` return type\n\nIf you want the table annotations to also expose the entity-aware `find()`\nreturn type for IDEs, enable:\n\n```php\n'IdeHelper' => [\n    'tableEntityQuery' => true,\n],\n```\n\nThis is intentionally optional, because finder result shapes can still widen\nbeyond plain entities.\n\n### Detailed param types\n\nThe `IdeHelper.genericsInParam` option is tri-state:\n\n- `false` (default) — bare `array` params, legacy behavior.\n- `true` — basic generics: `array<mixed>` / `array<string, mixed>` / `iterable<TEntity>`.\n- `'detailed'` — fully detailed types throughout, matching the richer form PHPStan and Psalm understand best.\n\nWith `'detailed'`, the generated method annotations look like:\n\n```php\n * @method \\App\\Model\\Entity\\User newEntity(array<string, mixed> $data, array<string, mixed> $options = [])\n * @method array<\\App\\Model\\Entity\\User> newEntities(array<array<string, mixed>> $data, array<string, mixed> $options = [])\n * @method \\App\\Model\\Entity\\User get(mixed $primaryKey, array<string, mixed>|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\App\\Model\\Entity\\User findOrCreate(\\Cake\\ORM\\Query\\SelectQuery<\\App\\Model\\Entity\\User>|callable|array<string, mixed> $search, ?callable $callback = null, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<int, \\App\\Model\\Entity\\User>|false saveMany(iterable<\\App\\Model\\Entity\\User> $entities, array<string, mixed> $options = [])\n```\n\nSwitching the value is additive — existing `true` users keep their current\noutput, and the new `'detailed'` opt-in can be enabled at any time.\n\n## Entities\n\nEntities annotate their properties and relations.\n\nA `Location` entity could look like this afterward:\n\n```php\n/**\n * @property int $id\n * @property int $user_id\n * @property \\App\\Model\\Entity\\User $user\n * @property string $location\n * @property string $details\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime $modified\n * @property string|null $virtual_property\n *\n * @property \\App\\Model\\Entity\\Image[] $images\n * @property \\App\\Model\\Entity\\User $user\n */\nclass Location extends Entity {\n}\n```\n\n### Custom type maps\n\nUsing the Configure key `'IdeHelper.typeMap'` you can set a custom array of\ntypes to be used for the field mapping. Overwriting the defaults of this\nplugin is also possible — to skip (reset) just set the value to `null`:\n\n```php\n'IdeHelper' => [\n    'typeMap' => [\n        'custom' => 'array',\n        'longtext' => null,\n        // ...\n    ],\n],\n```\n\nUsing the Configure key `'IdeHelper.nullableMap'` you can set a custom array\nof types and whether they can be nullable:\n\n```php\n'IdeHelper' => [\n    'nullableMap' => [\n        'custom' => false,\n        'longtext' => true,\n        // ...\n    ],\n],\n```\n\n### Virtual properties\n\nFor virtual properties the annotator looks up the respective `_get...()`\nmethods (e.g. `_getVirtualProperty()` for `$virtual_property`). It first checks\nthe documented type in the doc block's `@return`, otherwise (given PHP 7.0+)\ntries to read it from the return type hint (e.g. `: ?string`). Only if that is\nalso not present does it fall back to `mixed`.\n\nNote: You can also use the `@property-read` tag if it is a pure virtual field\ngetter.\n"
  },
  {
    "path": "docs/annotations/operations.md",
    "content": "# Operations\n\nCross-cutting flags and workflows that apply to every annotator subcommand.\n\n## Running All Commands\n\n```bash\nbin/cake annotate all\n```\n\nBy default it runs interactively, asking you for each class type whether to\ncontinue. You can use `-i` (interactive) to enable interactive mode. It is\nalso recommended to make the output more verbose:\n\n```bash\nbin/cake annotate all -i -v\n```\n\n::: warning Backup first\nMake sure you have committed or backed up all project files before running\nthe annotator across the whole project.\n:::\n\n## Dry-Run and Diff\n\nIf you want to check whether the annotator would modify any files, run it\nwith `-d` (dry-run):\n\n```bash\nbin/cake annotate all -d\n```\n\nIt will output a small diff for each modification:\n\n```\ntemplates/Tickets\n-> view\n   | +<?php\n   | +/**\n   | + * @var \\App\\View\\AppView $this\n   | + * @var \\App\\Model\\Entity\\Ticket $ticket\n   | + */\n   | +?>\n   |  <nav class=\"large-3 medium-4 columns\" id=\"actions-sidebar\">\n```\n\n::: tip Verbose dry-runs\nUse `-v` together with `-d` to get more information on which files were\nprocessed.\n:::\n\n## Quick-Filter Files\n\nWith `-f` / `--filter` you can quickly annotate only specific files. The\nfilter is applied to the file name. For templates it also looks at the folder\nname.\n\n## Specific Files Only\n\nWith `--file` and a comma-separated list of ROOT-relative or absolute paths,\nyou can limit the run to those specific files. This is useful for programmatic\ntooling-based runs or file watchers.\n\nIf `--file` is set, it takes precedence over `--filter` (which is ignored).\n\nExample:\n\n```bash\nbin/cake annotate all --file src/View/AppView.php,plugins/My/src/View/Helper/MyHelper.php\n```\n\n## Removing Outdated Annotations\n\nWith `-r` / `--remove` there is basic support for finding and removing\noutdated annotations.\n\n::: warning Alpha-quality feature\nOnly use this after running the normal annotation flow and committing the\nresult, so you can review and verify the changes. This feature is still in a\nvery alpha phase.\n:::\n\nYou can prevent removal (just like updating) by adding a comment to your\nannotation. That will skip any attempt to remove it:\n\n```php\n    @property array|null $data !\n```\n\nor:\n\n```php\n    @property array|null $data ! A manual field for testing only\n```\n\n## Skipping Annotations for a Class\n\nSometimes you are extending another class. In that case you can use the\n`@inherit` tag in the class doc block to skip annotating:\n\n```php\n/**\n * @inheritdoc\n */\nclass CustomImagesTable extends ImagesTable ...\n```\n\nIn this case `CustomImagesTable` extends `ImagesTable` but uses the same\n`protected $_entityClass = Image::class;`, so we skip annotating.\n\n## File Watcher Setup\n\nYou can set up a file watcher to run the annotation tool on every file\nchange, giving you live annotation updates while coding.\n\n### Using Node and Chokidar\n\nInstall in your project root:\n\n```bash\nnpm init -y\nnpm install chokidar --save\n```\n\nRun:\n\n```bash\nnode vendor/dereuromark/cakephp-ide-helper/annotate-watcher.cjs\n```\n\nIf necessary, you can customize the paths via `--path=src/,templates/`, for\nexample. You can also copy `annotate-watcher.cjs` to your app and customize it\nthere.\n\nSince this is cross-platform, this is the recommended approach. It is also\nthe most performant — only files directly modified are touched. The trade-off\nis that it might miss a few related templates that are not modified but would\nget updates.\n\n### Using watchexec\n\nSee [github.com/watchexec/watchexec](https://github.com/watchexec/watchexec).\n\n```bash\nwatchexec -e php 'bin/cake annotate all'\n```\n\nHere you would run the annotator over all files. Still usually quite\nperformant.\n\n## Continuous Integration\n\nThe tool can be run like the coding standards check in your CI. This way no\nannotation can be forgotten when making PRs.\n\nUse the `--ci` option along with `-d` (dry run):\n\n```bash\nbin/cake annotate all -d --ci\n```\n\nIt will return error code `2` if any modification has to be done.\n\n::: info Database setup\nThis needs some additional setup, like running migrations prior to the call.\nThe database must exist and replicate the actual DB.\n:::\n\nYou can also add this into a pre-commit hook for local development. Your VCS\nwill then refuse to commit until annotations are all in line.\n\n## Writing Your Own Annotators\n\nExtend `IdeHelper\\Command\\AnnotateCommand` at the application level, register\nyour command, and create your own `Annotator` class:\n\n```php\nclass MyAnnotator extends AbstractAnnotator {\n\n    /**\n     * @param string $path\n     * @return bool\n     */\n    public function annotate(string $path): bool {\n        // ...\n    }\n}\n```\n\nThen read a folder, iterate over it, and invoke your annotator from the\ncommand with a specific path.\n\n## Configure Options\n\nFor the full list of possible Configure options, see the `app.example.php`\nfile in the plugin's `/config/` directory. The content can be directly copied\ninto your project config.\n"
  },
  {
    "path": "docs/annotations/templates.md",
    "content": "# Templates\n\nAnnotate view templates and elements:\n\n```bash\nbin/cake annotate templates\n```\n\nTemplates should have a `/** @var \\App\\View\\AppView $this */` added on top if\nthey use any helper or access the request object. They should also annotate\nentities they use.\n\nA template such as:\n\n```php\n<h2>Some header</h2>\n<?php echo $this->Form->create($user); ?>\n<?php foreach ($groups as $group): ?>\n<?php endforeach; ?>\n<li><?= $this->Html->link(__('Edit Email'), ['action' => 'edit', $email->id]) ?> </li>\n```\n\nwould get the following added on top:\n\n```php\n<?php\n/**\n * @var \\App\\View\\AppView $this\n * @var \\App\\Model\\Entity\\Email $email\n * @var \\App\\Model\\Entity\\Group[] $groups\n * @var \\App\\Model\\Entity\\User $user\n */\n?>\n```\n\n## Extensions\n\nTo adjust which template extensions are processed, set\n`IdeHelper.templateExtensions` via Configure. By default, all files of type\n`'ctp'` and `'php'` will be checked.\n\n::: info Twig templates\nAll template annotating is around PHP templates. Twig templates are not\nsupported. Twig usually has its own tooling — but it has serious drawbacks on\nwhat this plugin provides: auto-complete and type-hinting as well as IDE\nintrospection of variable types.\n:::\n\n## Skipping Folders\n\nCertain template folders, like Bake template generation, should be skipped.\nThis is done by default for `/templates/Bake/` in your app or your plugin.\n\nIf you want to adjust this, set `IdeHelper.skipTemplatePaths` via Configure:\n\n```php\n'IdeHelper' => [\n    'skipTemplatePaths' => [\n        // ...\n    ],\n],\n```\n\n## Skipping Variables\n\nIn some cases existing annotations might match different entities (e.g.\nplugin vs. app namespace). To prevent them from being replaced incorrectly,\nmark them to be ignored by adding any comment description:\n\n```php\n<?php\n/**\n * @var \\App\\View\\AppView $this\n * @var \\My\\Custom\\Entity $car !\n */\n?>\n```\n\nThe `!` prevents the entity annotation from being replaced.\n\n## Auto-Collecting Variables\n\nThe IdeHelper can auto-collect template variables and add them to the list\nabove. Set `'IdeHelper.autoCollect'` to `false` to disable this. It defaults\nto `'mixed'` where the type cannot be guessed/detected.\n\nIf you need more control, configure a callable to detect or guess:\n\n```php\n'IdeHelper.autoCollect', function(array $variable) {\n    if ($variable['name'] === 'date') {\n        return 'Cake\\I18n\\DateTime';\n    }\n    return null;\n});\n```\n\n::: tip Unique variables\nFor the best experience of auto-collecting, use unique variable names inside\nthe template(s). If you pass down a `$user` variable from the controller,\nmake sure you are not overwriting it in some local scope.\n\n```php\n// This will skip the other $user annotation\nforeach ($role->users as $user) {}\n\n// Use a better name instead to keep $user annotation\nforeach ($role->users as $rolUser) {}\n```\n:::\n\nYou can use the `'IdeHelper.autoCollectBlacklist'` config to exclude certain\nvariables. The array accepts both strings and regexp patterns like\n`'/^\\_.+$/i'` for underscore-prefixed variables.\n\n## Entity Collections\n\nUsually, all collections (pagination, find) are object collections when passed\nto the view layer. The template annotations added for them are e.g.:\n\n```\n// objectAsGenerics false\n@var \\App\\Model\\Entity\\Article[]|\\Cake\\Collection\\CollectionInterface $articles\n\n// objectAsGenerics true\n@var \\Cake\\Collection\\CollectionInterface<\\App\\Model\\Entity\\Article> $articles\n```\n\nThe config `IdeHelper.templateCollectionObject` can be set to a FQCN string if\nyou want to display a custom class (e.g. `\\Cake\\Datasource\\ResultSetInterface`).\nYou can also set it to `iterable` (recommended) if you don't use any of the\nspecific interface methods (just iterating over them):\n\n```\n// templateCollectionObject set to 'iterable'\n@var iterable<\\App\\Model\\Entity\\Article> $articles\n```\n\nIf you always pass arrays, you can set `IdeHelper.templateCollectionObject` to\n`false` to reflect this in the annotations:\n\n```\n// arrayAsGenerics false\n@var \\App\\Model\\Entity\\Article[] $articles\n\n// arrayAsGenerics true\n@var array<\\App\\Model\\Entity\\Article> $articles\n```\n\n## Preemptive Annotating\n\nUsing Configure key `'IdeHelper.preemptive'` set to `true` you can be a bit\nmore preemptive in annotations. E.g. `@var \\App\\View\\AppView $this` will then\nalways be added to view templates, even if not currently needed. This allows\nimmediate type-hinting once actually needed; it is recommended to enable this\nsetting.\n\n## Custom View Class\n\nUsing Configure key `'IdeHelper.viewClass'` a custom class name can be set to\nuse instead of the default. E.g. `'App\\View\\MyCustomAppView'` or\n`MyCustomAppView::class` (incl. `use` statement).\n"
  },
  {
    "path": "docs/annotations/view.md",
    "content": "# View, Components, Helpers\n\n## View\n\nThe `AppView` class should annotate the helpers of the plugins and the app.\n\n```bash\nbin/cake annotate view\n```\n\nWith template content like:\n\n```php\n<?php echo $this->My->foo($bar); ?>\n<?php if ($this->Configure->baz()) {} ?>\n```\n\nthe following would be annotated (if the `My` and `Shim.Configure` helpers are\nloaded correctly):\n\n```php\n/**\n * @property \\App\\View\\Helper\\MyHelper $My\n * @property \\Shim\\View\\Helper\\ConfigureHelper $Configure\n */\nclass AppView extends View {\n}\n```\n\n### Include plugins\n\nUsing the Configure key `'IdeHelper.includedPlugins'` you can set an array of\n(loaded!) plugins to include. Those will then also be parsed and all found\nhelpers added to the `AppView` annotations. Setting this to `true` will\nauto-include all loaded plugins.\n\n## Components\n\nComponents should annotate any component they use.\n\n```bash\nbin/cake annotate components\n```\n\nA component containing:\n\n```php\n    /**\n     * @var array\n     */\n    protected $components = [\n        'RequestHandler',\n        'Flash.Flash',\n    ];\n```\n\nwould get the following annotations:\n\n```php\n/**\n * @property \\App\\Controller\\Component\\CheckHttpCacheComponent $CheckHttpCache\n * @property \\Flash\\Controller\\Component\\FlashComponent $Flash\n */\n```\n\n## Helpers\n\nHelpers should annotate any helper they use.\n\n```bash\nbin/cake annotate helpers\n```\n\nA helper containing:\n\n```php\n    /**\n     * @var array\n     */\n    protected $helpers = [\n        'Form',\n    ];\n\n    /**\n     * @param \\Cake\\View\\View $View\n     * @param array $config\n     */\n    public function __construct(View $View, array $config = []) {\n        parent::__construct($View, $config);\n        $this->_View->loadHelper('Template');\n    }\n```\n\nwould get the following annotations:\n\n```php\n/**\n * @property \\Cake\\View\\Helper\\FormHelper $Form\n * @property \\App\\View\\Helper\\TemplateHelper $Template\n */\n```\n"
  },
  {
    "path": "docs/code-completion/index.md",
    "content": "# Code Completion File Generator\n\nIn contrast to the [PhpStorm meta file generator](/generator/), this tool is\nintentionally generic and IDE agnostic.\n\n![Behavior Code Completion](/img/code_completion.png)\n\n## Usage\n\nThis command will generate the CodeCompletion `php` files into your app's\n`TMP/` directory:\n\n```bash\nbin/cake generate code_completion\n```\n\nThe files should not be persisted — they will always be regenerated or\nupdated locally if needed.\n\n::: tip composer hook\nSet up a `post-install-cmd` hook for composer to keep them up to date\nautomatically.\n:::\n\n## Behaviors\n\n```php\n/** @var \\Search\\Manager $searchManager */\n$searchManager = $this->behaviors()->Search->searchManager();\n```\n\nSo far `$searchManager` required the annotation above to be type-hinted and\nclickable, because the magic property access is not resolvable on its own.\n\nWith the generated code completion file this is no longer necessary. The\nproperty `Search` is detected as `\\Search\\Model\\Behavior\\SearchBehavior`,\nmaking `searchManager()` available in the IDE for method argument checking\nand following.\n\n## SelectQuery generics\n\nThe code completion generator also ships a `Cake\\ORM\\Query\\SelectQuery`\nhelper stub for fluent query chains. This is especially useful together with\nthe model annotation option:\n\n```php\n'IdeHelper' => [\n    'tableEntityQuery' => true,\n],\n```\n\nThat combination lets IDEs preserve the concrete entity type through calls\nsuch as:\n\n```php\n$query = $this->Users->find();\n$query->where(['active' => true])->all();\n```\n\nThe generated stub intentionally focuses on methods where the subject type is\nstable or where Cake already has a clear subject transition:\n\n| Flow | Semantic result | ide-helper support |\n| --- | --- | --- |\n| `$this->Users->find()->where(...)->all()` | `User` entities | Covered via `tableEntityQuery` + `SelectQuery<TSubject>` stub |\n| `$this->Users->find('active')->matching('Roles')->contain('Profiles')->all()` | `User` entities | Covered; these fluent methods preserve `TSubject` |\n| `$this->Users->find()->disableHydration()->all()` | `array<string, mixed>` rows | Covered; `disableHydration()` switches the query stub to array results |\n| `$this->Users->find('list')->all()` | non-entity shaped result | Not forced by default; keep this outside the entity-query assumption |\n| `$this->Users->find()->formatResults(...)` | depends on formatter | Not modeled; formatter callbacks can reshape results arbitrarily |\n| `$this->Users->find()->mapReduce(...)` | depends on mapper/reducer | Not modeled; map/reduce can reshape results arbitrarily |\n\nThis keeps the default helper honest: preserve the type where the query\nstays subject-compatible, and avoid pretending that formatter-driven or\nlist-style flows are still plain entity queries.\n\nFor PhpStorm projects you can point the generated code completion files into\n`.phpstorm.meta.php/` so they are indexed as local project helpers:\n\n```php\n'IdeHelper' => [\n    'codeCompletionPath' => ROOT . DS . '.phpstorm.meta.php' . DS,\n],\n```\n\nThen regenerate the files with:\n\n```bash\nbin/cake generate code_completion\n```\n\n## Adding Your Own Tasks\n\nCreate your own task class:\n\n```php\nnamespace App\\CodeCompletion\\Task;\n\nuse IdeHelper\\CodeCompletion\\Task\\TaskInterface;\n\nclass MyTask implements TaskInterface {\n\n    const TYPE_NAMESPACE = 'Some\\Namespace';\n\n    /**\n     * @return string\n     */\n    public function type(): string {\n        return static::TYPE_NAMESPACE;\n    }\n\n    /**\n     * @return string\n     */\n    public function create(): string {\n        // ...\n    }\n\n}\n```\n\nThen add it to the config:\n\n```php\n'IdeHelper' => [\n    'codeCompletionTasks' => [\n        'MyTask' => \\App\\CodeCompletion\\Task\\MyTask::class,\n    ],\n],\n```\n\nThe key `'MyTask'` can be any string.\n\n### Replacing native tasks\n\nUsing associative arrays you can swap any native task with your own\nimplementation:\n\n```php\n'IdeHelper' => [\n    'codeCompletionTasks' => [\n        \\IdeHelper\\CodeCompletion\\Task\\BehaviorTask::class => \\App\\CodeCompletion\\Task\\MyEnhancedBehaviorTask::class,\n    ],\n],\n```\n\nThe native class name is the key, your replacement the value. Setting the\nvalue to `null` disables a native task entirely.\n\n### Property example\n\nSo let's imagine you have the following magic properties you want to annotate:\n\n```php\n$alpha = $someObject->Alpha; // Returns \\My\\Cool\\Alpha class\n$beta = $someObject->Beta; // Returns \\My\\Cool\\Beta class\n```\n\nThen make sure your task's `create()` method returns something like:\n\n```php\nabstract class SomeObject extends SomeObjectInterface {\n\n    /**\n     * Alpha class.\n     *\n     * @var \\My\\Cool\\Alpha\n     */\n    public $Alpha;\n\n    /**\n     * Beta class.\n     *\n     * @var \\My\\Cool\\Beta\n     */\n    public $Beta;\n\n}\n```\n\nWe use the `abstract` keyword to avoid direct implementation hinting.\n\n### Method example\n\nLet's imagine you have the following magic methods you want to annotate:\n\n```php\n$alpha = $someObject->alpha(); // Returns \\My\\Cool\\Alpha class\n$beta = $someObject->beta(); // Returns \\My\\Cool\\Beta class\n```\n\nThen make sure your task's `create()` method returns something like:\n\n```php\nabstract class SomeObject extends SomeObjectInterface {\n\n    /**\n     * Alpha class.\n     *\n     * @var \\My\\Cool\\Alpha\n     */\n    protected $alpha;\n\n    // ...\n\n    /**\n     * @return \\My\\Cool\\Alpha\n     */\n    public function search(): Alpha {\n        return $this->alpha;\n    }\n\n    // ...\n\n}\n```\n\n## Custom Path for Files\n\nUsing the Configure key `'IdeHelper.codeCompletionPath'` you can use a custom\npath in your project. This way the files can be added to version control.\n"
  },
  {
    "path": "docs/generator/custom-tasks.md",
    "content": "# Custom Tasks and Directives\n\n## Adding Your Own Tasks\n\nCreate your own task class:\n\n```php\nnamespace App\\Generator\\Task;\n\nuse IdeHelper\\Generator\\Task\\TaskInterface;\n\nclass MyTask implements TaskInterface {\n\n    /**\n     * @return array<\\IdeHelper\\Generator\\Directive\\BaseDirective>\n     */\n    public function collect(): array {\n        // ...\n    }\n\n}\n```\n\nThen add it to the config:\n\n```php\n'IdeHelper' => [\n    'generatorTasks' => [\n        'MyTask' => \\App\\Generator\\Task\\MyTask::class,\n    ],\n],\n```\n\nThe key `'MyTask'` can be any string, but it must be unique across all\nexisting tasks.\n\n## Replacing Native Tasks\n\nUsing associative arrays you can swap any native task with your own\nimplementation:\n\n```php\n'IdeHelper' => [\n    'generatorTasks' => [\n        \\IdeHelper\\Generator\\Task\\ModelTask::class => \\App\\Generator\\Task\\MyEnhancedModelTask::class,\n    ],\n],\n```\n\nThe native class name is the key, your replacement the value. Setting the\nvalue to `null` disables a native task entirely.\n\n## Available Directives\n\n### Override\n\nMost directives used by the built-in tasks are `Override`. They are also the\nones supported the longest. For a specific string method argument, an\n`Override` returns a specific object — that covers a lot of CakePHP's\ninternal magic.\n\n```php\n$method = '\\Namespace\\PackageName\\MyFactory::create(0)';\n$map = [\n    'alpha' => '\\My\\Cool\\Alpha::class',\n    'beta' => '\\My\\Cool\\Beta::class',\n];\n$directive = new Override($method, $map);\n```\n\nYou can also use the `ClassName` value object together with real `::class`\nusage and imports:\n\n```php\nuse IdeHelper\\ValueObject\\ClassName;\nuse My\\Cool\\Alpha;\nuse My\\Cool\\Beta;\n\n$map = [\n    'alpha' => ClassName::create(Alpha::class),\n    'beta' => ClassName::create(Beta::class),\n];\n```\n\n### ExpectedArguments\n\nWith this you can set default values to choose from for method arguments.\nSpecify the parameter count as a 0-based value.\n\n```php\n$method = '\\Namespace\\PackageName\\MyFactory::create()';\n$position = 0;\n$list = [\n    '\\'alpha\\'',\n    '\\'beta\\'',\n];\n$directive = new ExpectedArguments($method, $position, $list);\n```\n\nNote the escaped quotes around literal string values. To make it cleaner,\nuse the `StringName` value object — it auto-quotes on output:\n\n```php\nuse IdeHelper\\ValueObject\\StringName;\n\n$list = [\n    StringName::create('alpha'),\n    StringName::create('beta'),\n];\n```\n\n### ExpectedReturnValues\n\nYou can also set expected return types for a method:\n\n```php\n$method = '\\Namespace\\PackageName\\MyFactory::create()';\n$list = [\n    '\\My\\Cool\\Alpha::class',\n    '\\My\\Cool\\Beta::class',\n];\n$directive = new ExpectedReturnValues($method, $list);\n```\n\n### RegisterArgumentsSet\n\nIf you reuse the same lists for both arguments and return values, you can\nregister a set and reuse it in the directives above.\n\n```php\n$set = 'mySet';\n$list = [\n    '\\My\\Cool\\Executer::SUCCESS',\n    '\\My\\Cool\\Executer::ERROR',\n];\n$directive = new RegisterArgumentsSet($set, $list);\n```\n\nNow you can use it as the list value `argumentsSet('mySet')` inside the\nothers. For this just pass the `$directive` object itself to the list, which\nthen contains only this one element.\n\nYou can also use the `LiteralName` value object for constants and anything\nthat does not need to be output as a string:\n\n```php\nuse IdeHelper\\ValueObject\\LiteralName;\n\n$list = [\n    LiteralName::create('\\My\\Cool\\Executer::SUCCESS'),\n    LiteralName::create('\\My\\Cool\\Executer::ERROR'),\n];\n```\n\nIf you want to reuse existing argument sets from other tasks, use the\n`ArgumentsSet` value object referencing them:\n\n```php\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\ValueObject\\ArgumentsSet;\n\n$method = '\\\\' . static::CLASS_FORMAT_HELPER . '::sidebarLink()';\n$list = [\n    ArgumentsSet::create(FormatIconFontAwesome5Task::SET_ICONS_FONTAWESOME),\n];\n$directive = new ExpectedArguments($method, 1, $list);\n```\n\nJust make sure those argument sets are actually available — this is not\nchecked for you.\n\n### ExitPoint\n\nThis directive lets the IDE know what methods abort the current code flow.\nThe IDE shows an \"Unreachable statement\" warning and usually highlights the\nfollowing code in yellow to inform you.\n\n```php\n$directive = new ExitPoint('\\My\\Class::method()');\n```\n\n## Worked Example\n\nImagine you have the following methods you want to annotate:\n\n```php\n$alpha = MyFactory::create('alpha'); // Returns \\My\\Cool\\Alpha class\n$beta = MyFactory::create('beta'); // Returns \\My\\Cool\\Beta class\n```\n\nCreate an `Override` to get the correct class instance returned:\n\n```php\n$method = '\\Namespace\\PackageName\\MyFactory::create(0)';\n$map = [\n    'alpha' => '\\My\\Cool\\Alpha::class',\n    'beta' => '\\My\\Cool\\Beta::class',\n];\n$override = new Override($method, $map);\n```\n\nNote that map keys are usually always strings and output auto-quoted by\ndefault. So you can treat them as simple/literal strings.\n\nNow imagine you have multiple class methods that return the same set of\nconstants. First create the reusable set:\n\n```php\n$list = [\n    '\\My\\Cool\\Executer::SUCCESS',\n    '\\My\\Cool\\Executer::ERROR',\n];\n$argumentsSet = new RegisterArgumentsSet('mySet', $list);\n```\n\nNow you can use it for all methods:\n\n```php\n$method = '\\My\\Cool\\Executer::execute()';\n$list = [\n    $argumentsSet,\n];\n$expectedReturnValues = new ExpectedReturnValues($method, $list);\n```\n\nMake sure your task's `collect()` method returns all of them:\n\n```php\nreturn [\n    $override->key() => $override,\n    $argumentsSet->key() => $argumentsSet,\n    $expectedReturnValues->key() => $expectedReturnValues,\n    // ...\n];\n```\n\nAs the key for directive values, always use their `->key()` string.\n\nFor more examples and details, see the [PhpStorm Advanced Metadata documentation](https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata).\n\n## Literal Keys\n\nIf you really need literal string keys (no auto-quoting), use the `KeyValue`\nvalue object:\n\n```php\n$key = ClassName::create(Bar::class);\n$value = ClassName::create(Bar::class);\n$keyValue = KeyValue::create($key, $value);\n\n// Now use it as any other value\n$map = [\n    'thisKeyIsOnlyForSortingNow' => $keyValue,\n    // ...\n];\n$directive = new Override('\\\\' . Table::class . '::returnMy(0)', $map);\n```\n\nIt allows you to control the quoting of both key and value. The map key here\nis only used for sorting.\n\n::: info Override-only\nThis value object can only be used for the `Override` directive — that's the\none that actually makes use of associative keys.\n:::\n"
  },
  {
    "path": "docs/generator/index.md",
    "content": "# Meta File Generator\n\n![Model Typehinting](/img/model_typehinting.png)\n\n![Model Autocomplete](/img/model_autocomplete.png)\n\nThe meta file generator produces `.phpstorm.meta.php` files that PhpStorm and\nVS Code (with PHP Intelephense) use to understand factories, magic strings,\nand return types throughout the CakePHP code base.\n\n## PhpStorm\n\nThis command generates `.ide-helper.meta.php` in your app's\n`ROOT/.phpstorm.meta.php/` directory:\n\n```bash\nbin/cake generate phpstorm\n```\n\nMake sure it is indexed (a restart of PhpStorm may be required).\n\n::: info Why a directory?\nWe use a directory here to allow custom and manually created meta files\nalongside this generated file. Any file inside this directory will be parsed\nand used. Prefixing the file with a `.` is recommended so PHPCS skips it\nautomatically.\n:::\n\n## What's Next\n\n- [Available Tasks](./tasks) — every built-in task and what it covers\n- [Custom Tasks and Directives](./custom-tasks) — add your own tasks, available directives, examples\n- [Operations](./operations) — include/exclude plugins, CI checks, reusing argument sets\n"
  },
  {
    "path": "docs/generator/operations.md",
    "content": "# Operations\n\nCross-cutting flags and workflows for the meta file generator.\n\n## Include/Exclude Plugins\n\nMany plugins do not need to be \"loaded\" — those would normally not be\nincluded in the generator tasks. If you want to add some not-loaded plugins\ninto the list of plugins to process, use:\n\n```php\n'IdeHelper' => [\n    'plugins' => [\n        'MyNotLoadedPlugin',\n        '-BlacklistedLoadedPlugin',\n    ],\n],\n```\n\nWith the `-` prefix you can even exclude loaded plugins from being processed.\n\n## CI or Pre-Commit Check\n\nUsing `-d` (dry run) you will get error code `2` if the file would need\nupdating:\n\n```bash\nbin/cake generate phpstorm -d\n```\n\nThis way you can automate the check for CI tooling or commit hooks.\n\n## Reusing Argument Sets\n\nYou can reuse argument sets that are present from any of the built-in or your\ncustom tasks.\n\nIn verbose mode the console gives you the available sets for re-use:\n\n```bash\nbin/cake generate phpstorm -v\n```\n\nYou can then directly make use of them in any matching directive (for such\nlists):\n\n- `ExpectedArguments`\n- `ExpectedReturnValues`\n"
  },
  {
    "path": "docs/generator/tasks.md",
    "content": "# Available Tasks\n\nThe list of built-in tasks shipped with the meta file generator.\n\n## Plugins\n\nIn your `Application.php` you can — after composer-requiring (and refreshing\nthe meta file) — auto-complete the available plugins for your `addPlugin()`\ncalls:\n\n```php\n    public function bootstrap(): void {\n        // ...\n        $this->addPlugin('TypeHere');\n    }\n```\n\nThis is especially useful for more complex and possibly vendor-prefixed\nnames (e.g. `'Cake/TwigView'`, note the forward slash).\n\n## Models\n\n```php\n/** @var \\App\\Model\\Table\\UsersTable $users */\n$users = TableRegistry::getTableLocator()->get('Users');\n$users->doSomething();\n```\n\nSo far `$users` required the annotation above to be type-hinted and\nclickable. With the generated meta file this is no longer necessary. The\nstatic factory call is detected and `$users` is hinted as\n`\\App\\Model\\Table\\UsersTable`, making `doSomething()` available in the IDE\nfor method argument checking and following.\n\nThis task also annotates dynamic model factory calls (e.g.\n`$this->getTableLocator()->get('Users')`) and `loadModel()` usage.\n\n![Model Autocomplete loadModel](/img/model_autocomplete_loadmodel.png)\n\nIf you prefer FQCN as the argument, you still get the benefit for the return\ntype:\n\n```php\nuse App\\Model\\Table\\UsersTable;\n\n$users = TableRegistry::getTableLocator()->get(UsersTable::class);\n$users->doSomething();\n```\n\nIt now knows the concrete object of `$users` and can autocomplete the method\ncall right away. You will not be able to quickly select from a list of input\noptions in this form, however.\n\n## Entities\n\nThe following are auto-completed, for example:\n\n```php\n$user->setDirty('field_name');\n$user->setError('field_name');\n$user->getOriginal('field_name');\n// ...\n```\n\n## TableAssociations\n\nThe following are auto-completed, for example:\n\n```php\n$this->belongsTo('Authors');\n$this->hasOne('Book');\n$this->hasMany('Articles');\n$this->belongsToMany('Tags.Tags');\n```\n\n## TableFinders\n\nThe `'threaded'` string is now auto-completed, for example:\n\n```php\n$this->Posts->find('threaded');\n```\n\n::: tip Preemptive finders\nUsing Configure key `'IdeHelper.preemptive'` set to `true`, you can be a bit\nmore verbose and include all possible custom finders, including those from\nbehaviors.\n:::\n\n![Model Autocomplete finder](/img/model_autocomplete_finder.png)\n\n## Behaviors\n\nThe following are auto-completed, for example:\n\n```php\n$this->addBehavior('Tools.Slugged');\n$this->removeBehavior('Slugged'); // Note the alias without plugin prefix\n```\n\n## Components\n\nThe following are auto-completed, for example:\n\n```php\n$this->loadComponent('My.Useful');\n$this->components()->unload('Useful'); // Note the alias without plugin prefix\n```\n\n## Helpers\n\nThe following are auto-completed, for example:\n\n```php\n$this->loadHelper('Tools.Tree');\n```\n\nAnd so is `addHelper()` (added in CakePHP 4.1) on the `ViewBuilder`:\n\n```php\n$this->viewBuilder()\n    ->addHelper('TinyAuth.AuthUser')\n    ->addHelper('Tools.Tree');\n```\n\n## Mailers\n\nThe following is auto-completed and returns the corresponding Mailer class:\n\n```php\n$userMailer = $this->getMailer('User');\n```\n\n## Types\n\nIn your bootstrap (app or plugin) you might add additional database `Type`\nclasses, or reconfigure existing ones:\n\n```php\nType::build('date')->useLocaleParser()->setLocaleFormat('d.m.Y');\nType::build('datetime')->useLocaleParser()->setLocaleFormat('d.m.Y H:i');\n```\n\nThe IDE recognizes the returned type of class and allows auto-complete here,\ntoo. Same for `Type::map()` and type strings like `integer`, `string`, etc.:\n\n```php\nType::map('decimal', ...);\n```\n\n## Elements\n\nHeavy users of elements in templates: tired of typing the full template name\nin `$this->element('...')` calls? PhpStorm auto-completes this, including all\nelements from plugins.\n\n## Layouts\n\n`$this->viewBuilder->setLayout(...)` is auto-completed.\n\n## Cache\n\n`Cache::write()`, `Cache::read()` and other methods are auto-completed for\nthe cache engine(s) available.\n\n## FormHelper\n\n`$this->Form->control()` is auto-completed for the model fields available.\n\n## Validation\n\n### `Validator::requirePresence()`\n\n![Validation Autocomplete Validator::requirePresence()](/img/validation_autocomplete_validator_require_presence.png)\n\nNow not just `bool`, but also the possible \"magic strings\" are type-hinted\nand usable as single click/enter.\n\n## Request params\n\n`$this->request->getParam()` auto-completes for `prefix`, `controller` and\nother common keys.\n\n## Configure keys\n\n![Configure Autocomplete](/img/configure_autocomplete.png)\n\n`Configure::read()` and the other methods are auto-completed for currently\nexisting keys. Numeric keys are excluded as they are usually not part of an\nassociative array config.\n\n## ENV keys\n\n`env()` is auto-completed for most common and used keys.\n\n## Translation keys\n\nUsing `__()` and `__d()` can be auto-completed based on your project's `.po`\nfiles.\n\n::: info Quoting limitation\nPhpStorm is [not smart enough yet](https://youtrack.jetbrains.com/issue/WI-52508)\nto auto-adjust any (escaped or not) quotes in your strings. So in those cases\nyou must use `'` as delimiters for your strings if you want auto-complete:\n\n```php\n<?php echo __('A \"quoted\" string'); ?>\n<?php echo __('A \\'literally quoted\\' string'); ?>\n<?php echo __('A variable \\'\\'{0}\\'\\' be replaced.', __('will')); ?>\n```\n\nAny further `'` inside will be escaped for you.\n:::\n\n## ConnectionManager\n\n`ConnectionManager::get()` is auto-completed for the currently configured\nconnection aliases.\n\n## Fixtures\n\n`TestCase::addFixture()` is auto-completed for the currently available\nfixtures from app, core, and plugins.\n\n## Migrations plugin database tables\n\nWhen using the Migrations plugin, this task comes in handy to quickly\nautocomplete existing tables, their column names, and possible column types.\n\nIt excludes CakePHP internal tables and all `phinxlog` ones by default. You\ncan use a regex blacklist to further exclude certain tables:\n\n```php\n'IdeHelper' => [\n    'skipDatabaseTables' => [\n        '/customRegexPattern/',\n        // ...\n    ],\n],\n```\n"
  },
  {
    "path": "docs/guide/ide-support.md",
    "content": "# IDE Support\n\nThis plugin is intended to work with **any IDE** that supports annotations and\ncode completion. Below is the current state of testing and integration.\n\n## Tested IDEs\n\nIDEs tested for full compatibility:\n\n- **[PhpStorm](https://github.com/dereuromark/cakephp-ide-helper/wiki/PHPStorm)** — also supports the meta file generator\n- IntelliJ IDEA\n- Atom\n- **[VS Code](https://github.com/dereuromark/cakephp-ide-helper/wiki/Visual-Studio-Code)** — meta file works via the PHP Intelephense plugin\n- Report or open a PR for your IDE on the [wiki](https://github.com/dereuromark/cakephp-ide-helper/wiki) to confirm full compatibility.\n\n## Plugins With Meta File Generator Tasks\n\nThe following plugins ship Generator tasks that build on top of this plugin:\n\n- [Migrations](https://github.com/cakephp/migrations) — migration file writing (included in IdeHelper directly).\n- [Queue](https://github.com/dereuromark/cakephp-queue) — `QueuedJobsTable::createJob()` usage.\n- [Burzum/CakeServiceLayer](https://github.com/burzum/cakephp-service-layer) — `loadService()` usage.\n- [CakephpFixtureFactories](https://github.com/vierge-noire/cakephp-fixture-factories) — factory class autocomplete.\n\nAdd yours via PR.\n\n## Plugins With Annotator Tasks\n\n- See the [IdeHelperExtra](https://github.com/dereuromark/cakephp-ide-helper-extra) plugin for a curated collection of additional annotator tasks.\n\n## Plugins With Illuminator Tasks\n\n- [StateMachine](https://github.com/spryker/cakephp-statemachine) — syncs states from XML into PHP.\n\n## Sponsorship\n\n[![PhpStorm logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/PhpStorm.svg)](https://jb.gg/OpenSourceSupport)\n\nJetBrains sponsors PhpStorm for the FOSS work on this repository and beyond.\n"
  },
  {
    "path": "docs/guide/index.md",
    "content": "# Introduction\n\n`cakephp-ide-helper` improves IDE compatibility and uses annotations to make\nyour IDE understand the \"magic\" of CakePHP, so you can click through class\nmethods and object chains and spot obvious issues and mistakes earlier. It\nalso improves compatibility with tools like [PHPStan](https://github.com/phpstan/phpstan).\n\n> Boost your productivity. Avoid mistakes.\n\nThis branch is for use with **CakePHP 5.1+**. For older versions see the\n[version map](https://github.com/dereuromark/cakephp-ide-helper/wiki#cakephp-version-map).\n\n## Two Tools, One Plugin\n\nThere are two main tools for keeping your code up to date:\n\n| Tool | Modifies | When to use |\n|------|----------|-------------|\n| **Annotator** | Doc blocks and annotations only — never functional code | Keep type hints in sync as the codebase evolves |\n| **Illuminator** | Functional code itself (constants, methods, etc.) | One-shot rewrites — for example, add entity field constants |\n\nOn top of those there are two stub-file generators:\n\n| Generator | Output | Audience |\n|-----------|--------|----------|\n| **Meta File Generator** | `.phpstorm.meta.php/.ide-helper.meta.php` | PhpStorm and VS Code (Intelephense) |\n| **Code Completion Generator** | Generic PHP stubs in `TMP/` | Any IDE that indexes PHP files |\n\nAnnotations are needed for static analyzers to understand the code; the meta\nfile and code completion stubs are mainly IDE autocomplete helpers.\n\n## What's Next\n\n1. [Installation](./installation) — Get the plugin set up\n2. [Usage](./usage) — High-level commands and recommended composer scripts\n3. [IDE Support](./ide-support) — Tested IDEs and plugins-of-this-plugin\n4. [Annotations](/annotations/) — Per-class-type annotation reference\n5. [Code Completion](/code-completion/), [Generator](/generator/), [Illuminator](/illuminator/)\n\nIf you are upgrading from 4.x see [Migrating from 4.x](./migration).\n"
  },
  {
    "path": "docs/guide/installation.md",
    "content": "# Installation\n\n## Composer\n\nInstall as a `require-dev` dependency:\n\n```bash\ncomposer require --dev dereuromark/cakephp-ide-helper\n```\n\n## Plugin Loading\n\nEnable the plugin in your `Application.php` or call:\n\n```bash\nbin/cake plugin load IdeHelper\n```\n\n::: tip Local-only loading\nAs a `require-dev` dependency, the plugin should only load for local\ndevelopment. Wrap the registration with a check or `try`/`catch`, and ideally\nalso restrict it to CLI mode (`if (PHP_SAPI === 'cli')`).\n:::\n\n## Verifying the Install\n\nOnce the plugin is loaded, the following commands become available:\n\n```bash\nbin/cake annotate --help\nbin/cake generate --help\nbin/cake illuminate --help\n```\n\nIf those resolve, you are ready to move on to [Usage](./usage).\n"
  },
  {
    "path": "docs/guide/migration.md",
    "content": "# Migrating from 4.x\n\nA few commands were renamed when moving to 5.x:\n\n| 4.x | 5.x |\n|-----|-----|\n| `bin/cake code_completion generate` | `bin/cake generate code_completion` |\n| `bin/cake phpstorm generate` | `bin/cake generate phpstorm` |\n| `bin/cake illuminator` | `bin/cake illuminate code` |\n\nIf you have composer scripts or CI jobs referencing the old commands, update\nthem accordingly.\n"
  },
  {
    "path": "docs/guide/usage.md",
    "content": "# Usage\n\nA short tour of the high-level commands. Each section has a dedicated guide\nwith the full set of options.\n\n## Annotations\n\nRun on your app:\n\n```bash\nbin/cake annotate [type]\n```\n\nBy default it prints a diff of the changes plus the number of modified lines.\n\nRun on a loaded plugin:\n\n```bash\nbin/cake annotate [type] -p FooBar\n```\n\nThe plugin is autoloaded if needed (when not manually loaded already).\n\nUse `-v` for verbose output:\n\n```bash\nbin/cake annotate [type] -v\n```\n\nAdd `-d` (`--dry-run`) to simulate the output without modifying files.\n\nSee [Annotations](/annotations/) for the full reference.\n\n## Code Completion\n\nThe code completion file aims to be generic and to work with all IDEs.\n\nGenerate the code completion files into `TMP/`:\n\n```bash\nbin/cake generate code_completion\n```\n\nSee [Code Completion](/code-completion/).\n\n## Meta File Generator\n\nThe meta file is supported by:\n\n- PhpStorm (2016.2+)\n- VS Code with the [PHP Intelephense](https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client) plugin\n\nGenerate the app-level `.phpstorm.meta.php` file:\n\n```bash\nbin/cake generate phpstorm\n```\n\nSee [Generator](/generator/).\n\n## Illuminator\n\nRewrite PHP files using configured Illuminator tasks:\n\n```bash\nbin/cake illuminate code <path>\n```\n\nUse `-v` for verbose output and `-t` (`--task`) with a comma-separated list to\nlimit the run to specific tasks. Add `-d` (`--dry-run`) to simulate.\n\nSee [Illuminator](/illuminator/).\n\n## Recommended composer Scripts\n\nGroup commands so they are easy to remember and easy to wire into hooks or CI:\n\n```json\n\"scripts\": {\n    \"setup\": \"bin/cake generate code_completion && bin/cake generate phpstorm\",\n    \"annotate\": \"bin/cake annotate all && bin/cake annotate all -p Sandbox\",\n    \"illuminate\": \"bin/cake illuminate code\"\n}\n```\n\nThat way you only have to remember the wrapper commands:\n\n- `composer setup` — also useful as a Git hook after checkout/pull\n- `composer annotate` — include all your `/plugins/` (the non-vendor ones)\n- `composer illuminate` — include all your `/plugins/` (the non-vendor ones)\n"
  },
  {
    "path": "docs/illuminator/index.md",
    "content": "# PHP File Illuminator\n\nThe Illuminator can modify your PHP files based on Illuminator rule sets. You\ncan use the pre-set tasks, or create your own to enhance your PHP files and\nclasses.\n\n::: warning Modifies functional code\nUnlike the [Annotator](/annotations/), which only updates doc blocks and\nannotations, the Illuminator actually modifies existing code. Make sure to\nback up or commit your changes before running it.\n:::\n\nEach task has its own scope defined, based on path or filename. If a task's\nscope does not match a file, it is skipped.\n\nUse `-p PluginName` to run inside a plugin.\n\n::: tip Plugin wildcards\nUse `*` to refer to a group of plugins, e.g. `-p SomePrefix/*` for everything\nunder your own `plugins/` directory. You can also use `all` for all app\nplugins.\n\nFor more than one plugin the command will not run into `vendor/` plugins, to\navoid accidental modification there.\n:::\n\n## Available Tasks\n\n### EntityField\n\nYour entities expose their fields either via `get()`/`set()` or as class\nproperties. Especially when using them through methods, you have no\ntype-hinting/autocomplete on those magic strings. In these cases, having\nclass constants is the solution.\n\nThis task adds them based on the defined property annotations in the doc\nblock:\n\n```php\n/**\n * @property int $id\n * @property string $brand_name\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $retired\n * @property \\App\\Model\\Entity\\Wheel[] $wheels\n */\nclass Car extends Entity {\n\n    public const FIELD_ID = 'id';\n    public const FIELD_BRAND_NAME = 'brand_name';\n    // ...\n\n}\n```\n\nThis is especially useful for code like:\n\n```php\n// old\n$carEntity->setDirty('wheels');\n\n// new\n$carEntity->setDirty($carEntity::FIELD_WHEELS);\n```\n\nor:\n\n```php\n// old\n$query->orderByDesc('publish_date');\n\n// new\n$query->orderByDesc(Post::FIELD_PUBLISH_DATE);\n```\n\nThis allows for less typing as autocomplete finds it immediately — and for\nusage display (IDE → right-click → get usage). That also means refactoring is\nmuch easier this way (via the IDE, usually a clean\none-modification-refactor across the whole project).\n\n::: info Visibility flag\nSince PHP 7.1+ this task adds the `public` visibility flag if you don't\nconfigure it otherwise.\n\nThis task does not clean out removed or renamed fields. You should quickly\ncheck for usage of the constant — if unused it can be safely removed.\n:::\n\n![Fields Autocomplete](/img/fields_autocomplete.png)\n\n## Adding Your Own Tasks\n\nCreate your own task class:\n\n```php\nnamespace App\\Illuminator\\Task;\n\nuse IdeHelper\\Illuminator\\Task\\TaskInterface;\n\nclass MyTask implements TaskInterface {\n\n    /**\n     * @param string $path\n     * @return bool\n     */\n    public function shouldRun(string $path): bool {\n        // ...\n    }\n\n    /**\n     * @param string $content\n     * @param string $path\n     * @return string\n     */\n    public function run(string $content, string $path): string {\n        // ...\n    }\n\n}\n```\n\nThen add it to the config:\n\n```php\n'IdeHelper' => [\n    'IlluminatorTasks' => [\n        'MyTask' => \\App\\Illuminator\\Task\\MyTask::class,\n    ],\n],\n```\n\nThe key `'MyTask'` can be any string, but it must be unique across all\nexisting tasks.\n\n### Replacing native tasks\n\nUsing associative arrays you can swap any native task with your own\nimplementation:\n\n```php\n'IdeHelper' => [\n    'IlluminatorTasks' => [\n        \\IdeHelper\\Illuminator\\Task\\FooBarTask::class => \\App\\Illuminator\\Task\\MyEnhancedFooBarTask::class,\n    ],\n],\n```\n\nThe native class name is the key, your replacement the value. Setting the\nvalue to `null` disables a native task entirely.\n\n## Configuration\n\nYou can specify specific settings via `app.php` config:\n\n- `'IdeHelper.illuminatorIndentation'` as `'    '` to use spaces as indentation\n  whitespace; defaults to `\"\\t\"`.\n\n## Important Constraint\n\nSome tasks may be based on the results of the Annotator. Make sure to run the\nAnnotator first, e.g.:\n\n```bash\nbin/cake annotate all && bin/cake illuminate code <path>\n```\n\n## CI or Pre-Commit Check\n\nUsing `-d` (dry run) you will get error code `2` if the file would need\nupdating. This way you can automate the check for CI tooling or commit hooks.\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\nlayout: home\n\nhero:\n  name: cakephp-ide-helper\n  text: IDE Helper for CakePHP\n  tagline: Annotations, meta files, code-completion stubs, and Illuminator tasks — boost IDE and static analyzer support, avoid mistakes.\n  image:\n    src: /logo.svg\n    alt: cakephp-ide-helper\n  actions:\n    - theme: brand\n      text: Get Started\n      link: /guide/\n    - theme: alt\n      text: Annotations\n      link: /annotations/\n    - theme: alt\n      text: View on GitHub\n      link: https://github.com/dereuromark/cakephp-ide-helper\n\nfeatures:\n  - icon: 📝\n    title: Annotations Updater\n    details: Keep doc blocks and annotations on controllers, models, entities, templates, commands, and more in sync as your code evolves.\n  - icon: 🧠\n    title: Meta File Generator\n    details: Produce `.phpstorm.meta.php` files so PhpStorm and VS Code understand factories, magic strings, and return types.\n  - icon: 💡\n    title: Code Completion Stubs\n    details: IDE-agnostic stub generation for behaviors and SelectQuery generics, so chained ORM calls keep their types.\n  - icon: ⚙️\n    title: Illuminator Tasks\n    details: Rewrite source files where annotations are not enough — for example, generate entity field constants automatically.\n  - icon: 🔁\n    title: CI and Watcher Friendly\n    details: Run as a CI check (`-d --ci`), pre-commit hook, or live file watcher with chokidar or watchexec.\n  - icon: 🧱\n    title: Extensible\n    details: Plug in your own annotators, callback tasks, generator tasks, and Illuminator tasks — replace built-ins or add new ones.\n---\n"
  },
  {
    "path": "docs/package.json",
    "content": "{\n  \"name\": \"cakephp-ide-helper-docs\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"docs:dev\": \"vitepress dev\",\n    \"docs:build\": \"vitepress build\",\n    \"docs:preview\": \"vitepress preview\"\n  },\n  \"devDependencies\": {\n    \"vitepress\": \"^1.6.3\",\n    \"vue\": \"^3.5.13\"\n  }\n}\n"
  },
  {
    "path": "docs/reference/configuration.md",
    "content": "# Configuration Reference\n\nAll Configure options live under the `IdeHelper` key in `app.php`. The\ncanonical reference with defaults is the `app.example.php` file in the\nplugin's `/config/` directory — that file is the source of truth and can be\ncopy-pasted into your project config.\n\n## Common Keys at a Glance\n\nThe list below highlights the keys mentioned across this documentation. For\nthe full set, defaults, and the most up-to-date options, see\n[`config/app.example.php`](https://github.com/dereuromark/cakephp-ide-helper/blob/master/config/app.example.php).\n\n### Annotator\n\n| Key | Type | Notes |\n|-----|------|-------|\n| `arrayAsGenerics` | `bool` | Use modern `array<type>` instead of legacy `type[]`. |\n| `genericsInParam` | `false \\| true \\| 'detailed'` | Tri-state for table method param types. See [Models](/annotations/models). |\n| `tableEntityQuery` | `bool` | Expose entity-aware `find()` return type on tables. |\n| `prefixes` | `array` | Whitelist of controller subfolder prefixes. |\n| `typeMap` | `array` | Map DB types → PHP types for entity property annotations. |\n| `nullableMap` | `array` | Map DB types → nullability flag. |\n| `includedPlugins` | `array \\| true` | Plugins to include when annotating helpers in `AppView`. |\n| `templateExtensions` | `array` | File extensions processed by the templates annotator. Defaults to `['ctp', 'php']`. |\n| `skipTemplatePaths` | `array` | Template folders the annotator should skip. `/templates/Bake/` is skipped by default. |\n| `templateCollectionObject` | `string \\| false` | FQCN (or `iterable`/`false`) used for template collection annotations. |\n| `autoCollect` | `bool \\| callable` | Auto-collect template variables. |\n| `autoCollectBlacklist` | `array` | Strings or regex patterns of variables to exclude from auto-collection. |\n| `preemptive` | `bool` | Preemptive annotations (e.g. always add `@var \\App\\View\\AppView $this` to templates). |\n| `viewClass` | `string` | Custom AppView FQCN. |\n| `preferLinkOverUsesInTests` | `bool` | Use `@link` (default) vs. `@uses` in test class annotations. |\n| `annotators` | `array` | Replace or disable native annotators. |\n| `classAnnotatorTasks` | `array` | Register or replace class-annotator tasks. |\n| `CallbackAnnotatorTasks` | `array` | Register or replace callback-annotator tasks. |\n\n### Generator\n\n| Key | Type | Notes |\n|-----|------|-------|\n| `plugins` | `array` | Include not-loaded plugins or exclude loaded ones (`-` prefix). |\n| `generatorTasks` | `array` | Register or replace generator tasks. |\n| `skipDatabaseTables` | `array` | Regex blacklist for the Migrations-tables generator task. |\n\n### Code Completion\n\n| Key | Type | Notes |\n|-----|------|-------|\n| `codeCompletionPath` | `string` | Custom output path for code completion files (e.g. `ROOT . DS . '.phpstorm.meta.php' . DS`). |\n| `codeCompletionTasks` | `array` | Register or replace code completion tasks. |\n\n### Illuminator\n\n| Key | Type | Notes |\n|-----|------|-------|\n| `illuminatorIndentation` | `string` | Indentation whitespace; defaults to `\"\\t\"`. Use `'    '` for spaces. |\n| `IlluminatorTasks` | `array` | Register or replace Illuminator tasks. |\n\n## Replacing or Disabling Native Tasks\n\nFor each task type, the registration array uses `'CustomKey' => ClassName`\nto add a task, or the native class name as the key to replace one:\n\n```php\n'IdeHelper' => [\n    'annotators' => [\n        // Replace a native annotator\n        \\IdeHelper\\Annotator\\EntityAnnotator::class => \\App\\Annotator\\MyEnhancedEntityAnnotator::class,\n        // Disable a native annotator\n        \\IdeHelper\\Annotator\\HelperAnnotator::class => null,\n        // Add a custom annotator\n        'MyCustomAnnotator' => \\App\\Annotator\\MyCustomAnnotator::class,\n    ],\n],\n```\n\nThe same pattern applies to `classAnnotatorTasks`, `CallbackAnnotatorTasks`,\n`generatorTasks`, `codeCompletionTasks`, and `IlluminatorTasks`.\n"
  },
  {
    "path": "docs/reference/contributing.md",
    "content": "# Contributing\n\n## Basics\n\nSee the composer scripts:\n\n```bash\ncomposer cs-check\ncomposer cs-fix\ncomposer test-setup\ncomposer test\n```\n\n## Generator\n\n### New meta file\n\nRun the test with the `--debug` option to generate a new `TMP/.meta.php`:\n\n```bash\nphp phpunit.phar tests/TestCase/Generator/PhpstormGeneratorTest.php --debug\n```\n\nYou can then easily copy it over into the `test_files/` directory and replace\nthe existing one.\n"
  },
  {
    "path": "phpcs.xml",
    "content": "<?xml version=\"1.0\"?>\n<ruleset name=\"plugin\">\n\t<arg name=\"cache\" value=\".phpcs.cache\"/>\n\t<rule ref=\"PSR2R\"/>\n\n    <arg value=\"nps\"/>\n\n    <file>config/</file>\n    <file>src/</file>\n    <file>tests/</file>\n\n    <exclude-pattern>/tests/test_files/</exclude-pattern>\n    <exclude-pattern>/tests/test_app/</exclude-pattern>\n</ruleset>\n"
  },
  {
    "path": "phpstan.neon",
    "content": "parameters:\n\tlevel: 8\n\tpaths:\n\t\t- src/\n\n\ttreatPhpDocTypesAsCertain: false\n\tbootstrapFiles:\n\t\t- tests/bootstrap.php\n\t\t- tests/shim.php\n\tignoreErrors:\n\t\t- '#Unsafe usage of new static\\(\\).+#'\n\t\t- '#Parameter \\#1 \\$object of function get_class expects object, object\\|string given.#'\n\t\t-\n\t\t\tmessage: '#(unknown class Phinx\\\\|invalid return type Phinx\\\\|undefined method Migrations\\\\Migrations)#'\n\t\t\tpath: src/Generator/Task/DatabaseTableColumnTypeTask.php\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n\t\t colors=\"true\"\n\t\t bootstrap=\"tests/bootstrap.php\"\n\t\t xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/10.2/phpunit.xsd\">\n\t<php>\n\t\t<ini name=\"memory_limit\" value=\"-1\"/>\n\t\t<!-- E_ALL => 32767 -->\n\t\t<!-- E_ALL & ~E_USER_DEPRECATED => 16383 -->\n\t\t<ini name=\"error_reporting\" value=\"32767\"/>\n\n\t\t<env name=\"FIXTURE_SCHEMA_METADATA\" value=\"tests/schema.php\"/>\n\t</php>\n\n\t<testsuites>\n\t\t<testsuite name=\"ide-helper\">\n\t\t\t<directory>tests/TestCase/</directory>\n\t\t</testsuite>\n\t</testsuites>\n\n\t<extensions>\n\t\t<bootstrap class=\"Cake\\TestSuite\\Fixture\\Extension\\PHPUnitExtension\"/>\n\t</extensions>\n\n\t<source>\n\t\t<include>\n\t\t\t<directory suffix=\".php\">src/</directory>\n\t\t</include>\n\t</source>\n</phpunit>\n"
  },
  {
    "path": "src/Annotation/AbstractAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nuse RuntimeException;\n\nabstract class AbstractAnnotation implements AnnotationInterface, ReplacableAnnotationInterface {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '';\n\n\tprotected string $type;\n\n\tprotected ?int $index;\n\n\t/**\n\t * Needed for removing annotations\n\t */\n\tprotected bool $isInUse = false;\n\n\t/**\n\t * @param string $type\n\t * @param int|null $index\n\t */\n\tpublic function __construct(string $type, ?int $index = null) {\n\t\t$this->type = $type;\n\t\t$this->index = $index;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function __toString(): string {\n\t\treturn static::TAG . ' ' . $this->build();\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getType(): string {\n\t\treturn $this->type;\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tpublic function hasIndex(): bool {\n\t\treturn $this->index !== null;\n\t}\n\n\t/**\n\t * @throws \\RuntimeException\n\t * @return int\n\t */\n\tpublic function getIndex(): int {\n\t\t$index = $this->index;\n\t\tif ($index === null) {\n\t\t\tthrow new RuntimeException('You cannot get an non-defined index. You can check with hasIndex() before calling this method.');\n\t\t}\n\n\t\treturn $index;\n\t}\n\n\t/**\n\t * @param bool $inUse\n\t *\n\t * @return void\n\t */\n\tpublic function setInUse(bool $inUse = true): void {\n\t\t$this->isInUse = $inUse;\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tpublic function isInUse(): bool {\n\t\treturn $this->isInUse;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/AnnotationFactory.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nuse RuntimeException;\n\nclass AnnotationFactory {\n\n\tprotected string $property;\n\n\t/**\n\t * @param string $tag\n\t * @param string $type\n\t * @param string|null $content\n\t * @param int|null $index\n\t * @return \\IdeHelper\\Annotation\\AbstractAnnotation|null\n\t */\n\tpublic static function create($tag, $type, $content = null, $index = null) {\n\t\tswitch ($tag) {\n\t\t\tcase PropertyAnnotation::TAG:\n\t\t\t\treturn new PropertyAnnotation($type, (string)$content, $index);\n\t\t\tcase PropertyReadAnnotation::TAG:\n\t\t\t\treturn new PropertyReadAnnotation($type, (string)$content, $index);\n\t\t\tcase MethodAnnotation::TAG:\n\t\t\t\treturn new MethodAnnotation($type, (string)$content, $index);\n\t\t\tcase VariableAnnotation::TAG:\n\t\t\t\treturn new VariableAnnotation($type, (string)$content, $index);\n\t\t\tcase MixinAnnotation::TAG:\n\t\t\t\tif ($content) {\n\t\t\t\t\t$type .= ' ' . $content;\n\t\t\t\t}\n\n\t\t\t\treturn new MixinAnnotation($type, $index);\n\t\t\tcase ParamAnnotation::TAG:\n\t\t\t\treturn new ParamAnnotation($type, (string)$content, $index);\n\t\t\tcase UsesAnnotation::TAG:\n\t\t\t\tif ($content) {\n\t\t\t\t\t$type .= ' ' . $content;\n\t\t\t\t}\n\n\t\t\t\treturn new UsesAnnotation($type, $index);\n\t\t\tcase ExtendsAnnotation::TAG:\n\t\t\t\tif ($content) {\n\t\t\t\t\t$type .= ' ' . $content;\n\t\t\t\t}\n\n\t\t\t\treturn new ExtendsAnnotation($type, $index);\n\t\t\tcase LinkAnnotation::TAG:\n\t\t\t\tif ($content) {\n\t\t\t\t\t$type .= ' ' . $content;\n\t\t\t\t}\n\n\t\t\t\treturn new LinkAnnotation($type, $index);\n\t\t\tcase SeeAnnotation::TAG:\n\t\t\t\tif ($content) {\n\t\t\t\t\t$type .= ' ' . $content;\n\t\t\t\t}\n\n\t\t\t\treturn new SeeAnnotation($type, $index);\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param string $annotation (e.g. `@method \\Foo\\Bar myMethod($x, $y)`)\n\t *\n\t * @return \\IdeHelper\\Annotation\\AbstractAnnotation|null\n\t */\n\tpublic static function createFromString($annotation) {\n\t\tpreg_match('/^@mixin (.+)\\s*(.+)?$/', $annotation, $matches);\n\t\tif ($matches) {\n\t\t\treturn static::create(MixinAnnotation::TAG, $matches[1]);\n\t\t}\n\t\tpreg_match('/^@uses (.+)\\s*(.+)?$/', $annotation, $matches);\n\t\tif ($matches) {\n\t\t\treturn static::create(UsesAnnotation::TAG, $matches[1]);\n\t\t}\n\t\tpreg_match('/^@extends ([^\\s!]+<.*?>)(?:\\s*([!]))?/', $annotation, $matches);\n\t\tif ($matches) {\n\t\t\t$string = implode(' ', $matches);\n\n\t\t\treturn static::create(ExtendsAnnotation::TAG, $string);\n\t\t}\n\n\t\t// Split `@tag <type> <rest>`: the type may contain spaces inside generic\n\t\t// brackets (e.g. `Foo<int, Bar>`), so naive `[^ ]+` would cut it short.\n\t\t// Walk the type char-by-char, tracking `<>` depth, and stop at the first\n\t\t// top-level space.\n\t\tif (preg_match('/^(@property|@property-read|@method|@var|@param) (.+)$/', $annotation, $matches)) {\n\t\t\t$tag = $matches[1];\n\t\t\t$rest = $matches[2];\n\t\t\t$depth = 0;\n\t\t\t$len = strlen($rest);\n\t\t\t$split = -1;\n\t\t\tfor ($i = 0; $i < $len; $i++) {\n\t\t\t\t$c = $rest[$i];\n\t\t\t\tif ($c === '<') {\n\t\t\t\t\t$depth++;\n\t\t\t\t} elseif ($c === '>') {\n\t\t\t\t\t$depth = max(0, $depth - 1);\n\t\t\t\t} elseif ($c === ' ' && $depth === 0) {\n\t\t\t\t\t$split = $i;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ($split <= 0) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t$type = substr($rest, 0, $split);\n\t\t\t$content = substr($rest, $split + 1);\n\n\t\t\treturn static::create($tag, $type, $content);\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param string $tag\n\t * @param string $type\n\t * @param string|null $content\n\t * @param int|null $index\n\t * @throws \\RuntimeException\n\t * @return \\IdeHelper\\Annotation\\AbstractAnnotation\n\t */\n\tpublic static function createOrFail($tag, $type, $content = null, $index = null) {\n\t\t$annotation = static::create($tag, $type, $content, $index);\n\t\tif (!$annotation) {\n\t\t\tthrow new RuntimeException('Cannot create annotation for tag ' . $tag . ', type ' . $type);\n\t\t}\n\n\t\treturn $annotation;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/AnnotationInterface.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\ninterface AnnotationInterface {\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string;\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string;\n\n}\n"
  },
  {
    "path": "src/Annotation/ExtendsAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass ExtendsAnnotation extends AbstractAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@extends';\n\n\tprotected string $description;\n\n\t/**\n\t * @param string $type\n\t * @param int|null $index\n\t */\n\tpublic function __construct(string $type, ?int $index = null) {\n\t\t$description = '';\n\n\t\t$closingBracket = strrpos($type, '>');\n\t\tif ($closingBracket) {\n\t\t\t$description = substr($type, $closingBracket + 1);\n\t\t\t$description = trim($description);\n\t\t\t$type = substr($type, 0, $closingBracket + 1);\n\t\t}\n\n\t\tparent::__construct($type, $index);\n\n\t\t$this->description = $description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string {\n\t\treturn $this->description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string {\n\t\t$description = $this->description !== '' ? (' ' . $this->description) : '';\n\n\t\treturn $this->type . $description;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\ExtendsAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool {\n\t\tif (!$annotation instanceof self) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Always matches as there can only be one per docblock\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\ExtendsAnnotation $annotation\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void {\n\t\t$this->type = $annotation->getType();\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/LinkAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass LinkAnnotation extends AbstractAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@link';\n\n\tprotected string $description;\n\n\t/**\n\t * @param string $type\n\t * @param int|null $index\n\t */\n\tpublic function __construct($type, $index = null) {\n\t\t$description = '';\n\t\tif (str_contains($type, ' ')) {\n\t\t\t[$type, $description] = explode(' ', $type, 2);\n\t\t}\n\n\t\tparent::__construct($type, $index);\n\n\t\tif (preg_match('/^(http|https):/', $type)) {\n\t\t\t$this->isInUse = true;\n\t\t}\n\n\t\t$this->description = $description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string {\n\t\treturn $this->description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string {\n\t\t$description = $this->description !== '' ? (' ' . $this->description) : '';\n\n\t\treturn $this->type . $description;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\MixinAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool {\n\t\tif (!$annotation instanceof self) {\n\t\t\treturn false;\n\t\t}\n\t\tif ($annotation->getType() !== $this->type) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\MixinAnnotation $annotation\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void {\n\t\t$this->type = $annotation->getType();\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/MethodAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass MethodAnnotation extends AbstractAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@method';\n\n\tprotected string $method;\n\n\tprotected string $description;\n\n\t/**\n\t * @param string $type\n\t * @param string $method\n\t * @param int|null $index\n\t */\n\tpublic function __construct($type, $method, $index = null) {\n\t\tparent::__construct($type, $index);\n\n\t\t$description = '';\n\t\t$closingBrace = strrpos($method, ') ');\n\t\tif ($closingBrace !== false && $closingBrace !== strlen($method) - 1) {\n\t\t\t$description = substr($method, $closingBrace + 2);\n\t\t\t$method = substr($method, 0, $closingBrace + 1);\n\t\t}\n\n\t\t$this->method = $method;\n\t\t$this->description = $description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getMethod(): string {\n\t\treturn $this->method;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string {\n\t\treturn $this->description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string {\n\t\t$description = $this->description !== '' ? (' ' . $this->description) : '';\n\n\t\treturn $this->type . ' ' . $this->method . $description;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\MethodAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool {\n\t\tif (!$annotation instanceof self) {\n\t\t\treturn false;\n\t\t}\n\t\t$methodName = substr($annotation->getMethod(), 0, strpos($annotation->getMethod(), '(') ?: 0);\n\t\tif ($methodName !== substr($this->method, 0, strpos($this->method, '(') ?: 0)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\MethodAnnotation $annotation\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void {\n\t\t$this->type = $annotation->getType();\n\t\t$this->method = $annotation->getMethod();\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/MixinAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass MixinAnnotation extends AbstractAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@mixin';\n\n\tprotected string $description;\n\n\t/**\n\t * @param string $type\n\t * @param int|null $index\n\t */\n\tpublic function __construct($type, $index = null) {\n\t\t$description = '';\n\t\tif (str_contains($type, ' ')) {\n\t\t\t[$type, $description] = explode(' ', $type, 2);\n\t\t}\n\n\t\tparent::__construct($type, $index);\n\n\t\t$this->description = $description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string {\n\t\treturn $this->description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string {\n\t\t$description = $this->description !== '' ? (' ' . $this->description) : '';\n\n\t\treturn $this->type . $description;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\MixinAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool {\n\t\tif (!$annotation instanceof self) {\n\t\t\treturn false;\n\t\t}\n\t\tif ($annotation->getType() !== $this->type) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\MixinAnnotation $annotation\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void {\n\t\t$this->type = $annotation->getType();\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/ParamAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass ParamAnnotation extends AbstractAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@param';\n\n\tprotected string $variable;\n\n\tprotected string $description;\n\n\t/**\n\t * @param string $type\n\t * @param string $variable\n\t * @param int|null $index\n\t */\n\tpublic function __construct($type, $variable, $index = null) {\n\t\tparent::__construct($type, $index);\n\n\t\t$description = '';\n\t\tif (str_contains($variable, ' ')) {\n\t\t\t[$variable, $description] = explode(' ', $variable, 2);\n\t\t}\n\n\t\t$this->variable = $variable;\n\t\t$this->description = $description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getVariable(): string {\n\t\treturn $this->variable;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string {\n\t\treturn $this->description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string {\n\t\t$description = $this->description !== '' ? (' ' . $this->description) : '';\n\n\t\treturn $this->type . ' ' . $this->variable . $description;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\ParamAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool {\n\t\tif (!$annotation instanceof self) {\n\t\t\treturn false;\n\t\t}\n\t\tif ($annotation->getVariable() !== $this->variable) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\ParamAnnotation $annotation\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void {\n\t\t$this->type = $annotation->getType();\n\t\t$this->variable = $annotation->getVariable();\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/PropertyAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass PropertyAnnotation extends AbstractAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@property';\n\n\tprotected string $property;\n\n\tprotected string $description;\n\n\t/**\n\t * @param string $type\n\t * @param string $property\n\t * @param int|null $index\n\t */\n\tpublic function __construct($type, $property, $index = null) {\n\t\tparent::__construct($type, $index);\n\n\t\t$description = '';\n\t\tif (str_contains($property, ' ')) {\n\t\t\t[$property, $description] = explode(' ', $property, 2);\n\t\t}\n\t\tif (!str_starts_with($property, '$')) {\n\t\t\t$property = '$' . $property;\n\t\t}\n\n\t\t$this->property = $property;\n\t\t$this->description = $description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getProperty(): string {\n\t\treturn $this->property;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string {\n\t\treturn $this->description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string {\n\t\t$description = $this->description !== '' ? (' ' . $this->description) : '';\n\n\t\treturn $this->type . ' ' . $this->property . $description;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\PropertyAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool {\n\t\tif (!$annotation instanceof self) {\n\t\t\treturn false;\n\t\t}\n\t\tif ($annotation->getProperty() !== $this->property) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\PropertyAnnotation $annotation\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void {\n\t\t$this->type = $annotation->getType();\n\t\t$this->property = $annotation->getProperty();\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/PropertyReadAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass PropertyReadAnnotation extends PropertyAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@property-read';\n\n}\n"
  },
  {
    "path": "src/Annotation/ReplacableAnnotationInterface.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\ninterface ReplacableAnnotationInterface {\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool;\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation $annotation\n\t *\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void;\n\n}\n"
  },
  {
    "path": "src/Annotation/SeeAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass SeeAnnotation extends AbstractAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@see';\n\n\tprotected string $description;\n\n\t/**\n\t * @param string $type\n\t * @param int|null $index\n\t */\n\tpublic function __construct($type, $index = null) {\n\t\t$description = '';\n\t\tif (str_contains($type, ' ')) {\n\t\t\t[$type, $description] = explode(' ', $type, 2);\n\t\t}\n\n\t\tparent::__construct($type, $index);\n\n\t\t$this->description = $description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string {\n\t\treturn $this->description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string {\n\t\t$description = $this->description !== '' ? (' ' . $this->description) : '';\n\n\t\treturn $this->type . $description;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\MixinAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool {\n\t\tif (!$annotation instanceof self) {\n\t\t\treturn false;\n\t\t}\n\t\tif ($annotation->getType() !== $this->type) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\MixinAnnotation $annotation\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void {\n\t\t$this->type = $annotation->getType();\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/UsesAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass UsesAnnotation extends AbstractAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@uses';\n\n\tprotected string $description;\n\n\t/**\n\t * @param string $type\n\t * @param int|null $index\n\t */\n\tpublic function __construct($type, $index = null) {\n\t\t$description = '';\n\t\tif (str_contains($type, ' ')) {\n\t\t\t[$type, $description] = explode(' ', $type, 2);\n\t\t}\n\n\t\tparent::__construct($type, $index);\n\n\t\t$this->description = $description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string {\n\t\treturn $this->description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string {\n\t\t$description = $this->description !== '' ? (' ' . $this->description) : '';\n\n\t\treturn $this->type . $description;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\MixinAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool {\n\t\tif (!$annotation instanceof self) {\n\t\t\treturn false;\n\t\t}\n\t\tif ($annotation->getType() !== $this->type) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\MixinAnnotation $annotation\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void {\n\t\t$this->type = $annotation->getType();\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotation/VariableAnnotation.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotation;\n\nclass VariableAnnotation extends AbstractAnnotation {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TAG = '@var';\n\n\tprotected string $variable;\n\n\tprotected string $description;\n\n\tprotected bool $guessed = false;\n\n\t/**\n\t * @param string $type\n\t * @param string $variable\n\t * @param int|null $index\n\t */\n\tpublic function __construct($type, $variable, $index = null) {\n\t\tparent::__construct($type, $index);\n\n\t\t$description = '';\n\t\tif (str_contains($variable, ' ')) {\n\t\t\t[$variable, $description] = explode(' ', $variable, 2);\n\t\t}\n\t\t$this->variable = $variable;\n\t\t$this->description = $description;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getVariable(): string {\n\t\treturn $this->variable;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function getDescription(): string {\n\t\treturn $this->description;\n\t}\n\n\t/**\n\t * @param bool $value\n\t *\n\t * @return $this\n\t */\n\tpublic function setGuessed($value) {\n\t\t$this->guessed = $value;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tpublic function getGuessed() {\n\t\treturn $this->guessed;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build(): string {\n\t\t$description = $this->description !== '' ? (' ' . $this->description) : '';\n\n\t\treturn $this->type . ' ' . $this->variable . $description;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation|\\IdeHelper\\Annotation\\VariableAnnotation $annotation\n\t *\n\t * @return bool\n\t */\n\tpublic function matches(AbstractAnnotation $annotation): bool {\n\t\tif (!$annotation instanceof self) {\n\t\t\treturn false;\n\t\t}\n\t\tif ($annotation->getVariable() !== $this->variable) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\VariableAnnotation $annotation\n\t * @return void\n\t */\n\tpublic function replaceWith(AbstractAnnotation $annotation): void {\n\t\t$newType = $annotation->getType();\n\n\t\t// Preserve |null from existing type when the new type doesn't have it\n\t\tif ($this->hasNull() && !$this->typeHasNull($newType)) {\n\t\t\t$newType .= '|null';\n\t\t}\n\n\t\t$this->type = $newType;\n\t\t$this->variable = $annotation->getVariable();\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tprotected function hasNull(): bool {\n\t\treturn $this->typeHasNull($this->type);\n\t}\n\n\t/**\n\t * @param string $type\n\t * @return bool\n\t */\n\tprotected function typeHasNull(string $type): bool {\n\t\treturn str_contains($type, '|null') || str_contains($type, 'null|') || $type === 'null';\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/AbstractAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Bake\\View\\Helper\\DocBlockHelper;\nuse Cake\\Core\\Configure;\nuse Cake\\Core\\InstanceConfigTrait;\nuse Cake\\View\\View;\nuse IdeHelper\\Annotation\\AbstractAnnotation;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\ExtendsAnnotation;\nuse IdeHelper\\Annotation\\LinkAnnotation;\nuse IdeHelper\\Annotation\\MethodAnnotation;\nuse IdeHelper\\Annotation\\MixinAnnotation;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse IdeHelper\\Annotation\\PropertyReadAnnotation;\nuse IdeHelper\\Annotation\\UsesAnnotation;\nuse IdeHelper\\Annotation\\VariableAnnotation;\nuse IdeHelper\\Annotator\\Traits\\DocBlockTrait;\nuse IdeHelper\\Annotator\\Traits\\FileTrait;\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\Utility\\App;\nuse PHP_CodeSniffer\\Config;\nuse PHP_CodeSniffer\\Files\\File;\nuse PHP_CodeSniffer\\Util\\Tokens;\nuse PHPStan\\PhpDocParser\\Ast\\PhpDoc\\InvalidTagValueNode;\nuse ReflectionClass;\nuse RuntimeException;\nuse SebastianBergmann\\Diff\\Differ;\nuse SebastianBergmann\\Diff\\Output\\DiffOnlyOutputBuilder;\n\n$composerVendorDir = getcwd() . DS . 'vendor';\n$codesnifferDir = 'squizlabs' . DS . 'php_codesniffer';\nif (!is_dir($composerVendorDir . DS . $codesnifferDir)) {\n\t$ideHelperDir = substr(__DIR__, 0, strpos(__DIR__, DS . 'cakephp-ide-helper') ?: 0);\n\t$composerVendorDir = dirname($ideHelperDir);\n}\n$manualAutoload = $composerVendorDir . DS . $codesnifferDir . DS . 'autoload.php';\nif (!class_exists(Config::class) && file_exists($manualAutoload)) {\n\trequire $manualAutoload;\n}\n\nabstract class AbstractAnnotator {\n\n\tuse FileTrait;\n\tuse InstanceConfigTrait;\n\tuse DocBlockTrait;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_DRY_RUN = 'dry-run';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_PLUGIN = 'plugin';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_NAMESPACE = 'namespace';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_VERBOSE = 'verbose';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_REMOVE = 'remove';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const COUNT_REMOVED = 'removed';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const COUNT_UPDATED = 'updated';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const COUNT_ADDED = 'added';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const COUNT_SKIPPED = 'skipped';\n\n\t/**\n\t * @var array<string>\n\t */\n\tpublic const TYPES = [\n\t\tPropertyAnnotation::TAG,\n\t\tPropertyReadAnnotation::TAG,\n\t\tVariableAnnotation::TAG,\n\t\tMethodAnnotation::TAG,\n\t\tMixinAnnotation::TAG,\n\t\tUsesAnnotation::TAG,\n\t\tLinkAnnotation::TAG,\n\t\tExtendsAnnotation::TAG,\n\t];\n\n\tpublic static bool $output = false;\n\n\tprotected Io $_io;\n\n\t/**\n\t * @var array<string, mixed>\n\t */\n\tprotected array $_defaultConfig = [\n\t\tself::CONFIG_PLUGIN => null,\n\t];\n\n\t/**\n\t * @param \\IdeHelper\\Console\\Io $io\n\t * @param array<string, mixed> $config\n\t */\n\tpublic function __construct(Io $io, array $config) {\n\t\t$this->_io = $io;\n\t\t$this->setConfig($config);\n\n\t\t$appNamespace = Configure::read('App.namespace', 'App');\n\t\t$namespace = $this->getConfig(static::CONFIG_PLUGIN) ?: $appNamespace;\n\t\t$namespace = str_replace('/', '\\\\', $namespace);\n\t\t$this->setConfig(static::CONFIG_NAMESPACE, $namespace);\n\t}\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tabstract public function annotate(string $path): bool;\n\n\t/**\n\t * @param string $oldContent\n\t * @param string $newContent\n\t * @return void\n\t */\n\tprotected function displayDiff(string $oldContent, string $newContent): void {\n\t\t$differ = new Differ(new DiffOnlyOutputBuilder());\n\t\t$array = $differ->diffToArray($oldContent, $newContent);\n\n\t\t$begin = null;\n\t\t$end = null;\n\t\t/**\n\t\t * @var int $key\n\t\t */\n\t\tforeach ($array as $key => $row) {\n\t\t\tif ($row[1] === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($begin === null) {\n\t\t\t\t$begin = $key;\n\t\t\t}\n\t\t\t$end = $key;\n\t\t}\n\t\tif ($begin === null) {\n\t\t\treturn;\n\t\t}\n\t\t$firstLineOfOutput = $begin > 0 ? $begin - 1 : 0;\n\t\t$lastLineOfOutput = count($array) - 1 > $end ? $end + 1 : $end;\n\n\t\tfor ($i = $firstLineOfOutput; $i <= $lastLineOfOutput; $i++) {\n\t\t\t$row = $array[$i];\n\n\t\t\t$char = ' ';\n\t\t\t$output = trim($row[0], \"\\n\\r\\0\\x0B\");\n\n\t\t\tif ($row[1] === 1) {\n\t\t\t\t$char = '+';\n\t\t\t\t$this->_io->info('   | ' . $char . $output);\n\t\t\t} elseif ($row[1] === 2) {\n\t\t\t\t$char = '-';\n\t\t\t\t$this->_io->out('<warning>' . '   | ' . $char . $output . '</warning>');\n\t\t\t} else {\n\t\t\t\t$this->_io->out('   | ' . $char . $output);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $contents\n\t * @throws \\RuntimeException When the file cannot be written.\n\t * @return void\n\t */\n\tprotected function storeFile(string $path, string $contents): void {\n\t\tstatic::$output = true;\n\n\t\tif ($this->getConfig(static::CONFIG_DRY_RUN)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (file_exists($path) && md5_file($path) === md5($contents)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (file_put_contents($path, $contents) === false) {\n\t\t\tthrow new RuntimeException(sprintf('Failed to write file `%s`.', $path));\n\t\t}\n\t}\n\n\t/**\n\t * @var array<string, int>\n\t */\n\tprotected array $_counter = [];\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $annotations\n\t *\n\t * @return bool\n\t */\n\tprotected function annotateContent(string $path, string $content, array $annotations): bool {\n\t\tif (!count($annotations)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$file = $this->getFile($path);\n\n\t\t$classOrTraitIndex = $file->findNext([T_CLASS, T_TRAIT], 0);\n\t\tif (!$classOrTraitIndex) {\n\t\t\treturn false;\n\t\t}\n\t\t$beginningOfLineIndex = $this->beginningOfLine($file, $classOrTraitIndex);\n\t\t$beginningOfLineIndex = $this->skipOverAttributes($file, $beginningOfLineIndex);\n\n\t\t$closeTagIndex = $this->findDocBlockCloseTagIndex($file, $beginningOfLineIndex);\n\t\t$this->resetCounter();\n\t\tif ($closeTagIndex && $this->shouldSkip($file, $closeTagIndex)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($closeTagIndex && !$this->isInlineDocBlock($file, $closeTagIndex)) {\n\t\t\t$newContent = $this->appendToExistingDocBlock($file, $closeTagIndex, $annotations);\n\t\t} else {\n\t\t\t$newContent = $this->addNewDocBlock($file, $beginningOfLineIndex, $annotations);\n\t\t}\n\n\t\tif ($newContent === $content) {\n\t\t\t$this->reportSkipped($path);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!$this->getConfig('verbose')) {\n\t\t\t$this->_io->out('-> ' . str_replace(ROOT . DS, '', $path));\n\t\t}\n\n\t\t$this->displayDiff($content, $newContent);\n\t\t$this->storeFile($path, $newContent);\n\n\t\t$this->report();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index First functional code after docblock\n\t *\n\t * @return int|null\n\t */\n\tprotected function findDocBlockCloseTagIndex(File $file, int $index): ?int {\n\t\t$prevCode = $file->findPrevious(Tokens::$emptyTokens, $index - 1, null, true);\n\t\tif (!$prevCode) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn $file->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $index - 1, $prevCode) ?: null;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $docBlockCloseIndex\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $annotations\n\t *\n\t * @throws \\RuntimeException\n\t *\n\t * @return string\n\t */\n\tprotected function appendToExistingDocBlock(File $file, int $docBlockCloseIndex, array &$annotations): string {\n\t\t$existingAnnotations = $this->parseExistingAnnotations($file, $docBlockCloseIndex);\n\n\t\t$generatedTags = array_unique(array_map(static function ($annotation) {\n\t\t\treturn $annotation::TAG;\n\t\t}, $annotations));\n\n\t\t$replacingAnnotations = [];\n\t\t$addingAnnotations = [];\n\t\tforeach ($annotations as $key => $annotation) {\n\t\t\tif (!is_object($annotation)) {\n\t\t\t\tthrow new RuntimeException('Must be object: ' . print_r($annotation, true));\n\t\t\t}\n\t\t\tif ($this->exists($annotation, $existingAnnotations)) {\n\t\t\t\tunset($annotations[$key]);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!$this->allowsReplacing($annotation, $existingAnnotations)) {\n\t\t\t\tunset($annotations[$key]);\n\t\t\t\t$this->_counter[static::COUNT_SKIPPED]++;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$toBeReplaced = $this->needsReplacing($annotation, $existingAnnotations);\n\t\t\tif (!$toBeReplaced) {\n\t\t\t\t$addingAnnotations[] = $annotation;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$replacingAnnotations[] = $toBeReplaced;\n\t\t}\n\n\t\t$tokens = $file->getTokens();\n\t\t$lastTagIndexOfPreviousLine = $docBlockCloseIndex;\n\t\twhile ($tokens[$lastTagIndexOfPreviousLine]['line'] === $tokens[$docBlockCloseIndex]['line']) {\n\t\t\t$lastTagIndexOfPreviousLine--;\n\t\t}\n\n\t\t$needsNewline = $this->needsNewLineInDocBlock($file, $lastTagIndexOfPreviousLine);\n\n\t\t$fixer = $this->getFixer($file);\n\n\t\t$fixer->beginChangeset();\n\n\t\tforeach ($replacingAnnotations as $annotation) {\n\t\t\t$fixer->replaceToken($annotation->getIndex(), $annotation->build());\n\t\t\t$this->_counter[static::COUNT_UPDATED]++;\n\t\t}\n\n\t\tif (count($addingAnnotations)) {\n\t\t\t$annotationString = $needsNewline ? ' *' . \"\\n\" : '';\n\t\t\tforeach ($addingAnnotations as $annotation) {\n\t\t\t\t$annotationString .= ' * ' . $annotation . \"\\n\";\n\t\t\t\t$this->_counter[static::COUNT_ADDED]++;\n\t\t\t}\n\n\t\t\t$fixer->addContent($lastTagIndexOfPreviousLine, $annotationString);\n\t\t}\n\n\t\tif ($this->getConfig(static::CONFIG_REMOVE)) {\n\t\t\tforeach ($existingAnnotations as $key => $existingAnnotation) {\n\t\t\t\tif ($existingAnnotation->isInUse()) {\n\t\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif ($existingAnnotation->getDescription() !== '') {\n\t\t\t\t\t$this->_counter[static::COUNT_SKIPPED]++;\n\t\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif ($generatedTags && !in_array($existingAnnotation::TAG, $generatedTags, true)) {\n\t\t\t\t\tunset($existingAnnotations[$key]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$removingAnnotations = $existingAnnotations;\n\t\t\tforeach ($removingAnnotations as $annotation) {\n\t\t\t\t$lastWhitespaceOfPreviousLine = $this->getLastWhitespaceOfPreviousLine($tokens, $annotation->getIndex());\n\t\t\t\t$index = $annotation->getIndex();\n\t\t\t\tfor ($i = $lastWhitespaceOfPreviousLine; $i <= $index; $i++) {\n\t\t\t\t\t$fixer->replaceToken($i, '');\n\t\t\t\t}\n\t\t\t\t$this->_counter[static::COUNT_REMOVED]++;\n\t\t\t}\n\t\t}\n\n\t\t$fixer->endChangeset();\n\n\t\treturn $fixer->getContents();\n\t}\n\n\t/**\n\t * @param array<array<string, mixed>> $tokens\n\t * @param int $index\n\t *\n\t * @return int\n\t */\n\tprotected function getLastWhitespaceOfPreviousLine(array $tokens, int $index): int {\n\t\t$currentLine = $tokens[$index]['line'];\n\t\t$index--;\n\t\twhile ($tokens[$index]['line'] === $currentLine) {\n\t\t\t$index--;\n\t\t}\n\n\t\treturn $index;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation $annotation\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $existingAnnotations\n\t * @return bool\n\t */\n\tprotected function exists(AbstractAnnotation $annotation, array &$existingAnnotations): bool {\n\t\tforeach ($existingAnnotations as $key => $existingAnnotation) {\n\t\t\tif ($existingAnnotation->build() === $annotation->build()) {\n\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\tif ($annotation instanceof PropertyAnnotation && $existingAnnotation instanceof PropertyAnnotation) {\n\t\t\t\tif ($annotation->getProperty() === $existingAnnotation->getProperty() && $annotation->getType() === $existingAnnotation->getType()) {\n\t\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Let's skip on existing ones that are only guessed.\n\t\t\tif ($annotation instanceof VariableAnnotation && $existingAnnotation instanceof VariableAnnotation) {\n\t\t\t\tif ($annotation->getVariable() === $existingAnnotation->getVariable() && $annotation->getGuessed()) {\n\t\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\t// Special case: $this variable should not be duplicated if types also match\n\t\t\t\tif ($annotation->getVariable() === '$this' && $existingAnnotation->getVariable() === '$this' && $annotation->getType() === $existingAnnotation->getType()) {\n\t\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation $annotation\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $existingAnnotations\n\t * @return \\IdeHelper\\Annotation\\AbstractAnnotation|null\n\t */\n\tprotected function needsReplacing(AbstractAnnotation $annotation, array &$existingAnnotations) {\n\t\tforeach ($existingAnnotations as $key => $existingAnnotation) {\n\t\t\tif ($existingAnnotation->matches($annotation)) {\n\t\t\t\t$newAnnotation = clone $existingAnnotation;\n\t\t\t\t$newAnnotation->replaceWith($annotation);\n\n\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\treturn $newAnnotation;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation $annotation\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $existingAnnotations\n\t * @return bool\n\t */\n\tprotected function allowsReplacing(AbstractAnnotation $annotation, array &$existingAnnotations): bool {\n\t\tforeach ($existingAnnotations as $key => $existingAnnotation) {\n\t\t\tif ($existingAnnotation->matches($annotation) && $existingAnnotation->getDescription() !== '') {\n\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $closeTagIndex\n\t * @param array<string> $types\n\t *\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function parseExistingAnnotations(File $file, int $closeTagIndex, array $types = self::TYPES): array {\n\t\t$tokens = $file->getTokens();\n\n\t\t/** @var int $startTagIndex */\n\t\t$startTagIndex = $tokens[$closeTagIndex]['comment_opener'];\n\n\t\t$annotations = [];\n\t\tfor ($i = $startTagIndex + 1; $i < $closeTagIndex; $i++) {\n\t\t\tif ($tokens[$i]['type'] !== 'T_DOC_COMMENT_TAG') {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (!in_array($tokens[$i]['content'], $types, true)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$classNameIndex = $i + 2;\n\n\t\t\tif ($tokens[$classNameIndex]['type'] !== 'T_DOC_COMMENT_STRING') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$content = $tokens[$classNameIndex]['content'];\n\n\t\t\t/** @var \\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\InvalidTagValueNode|\\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\ReturnTagValueNode $valueNode */\n\t\t\t$valueNode = static::getValueNode($tokens[$i]['content'], $content);\n\t\t\tif ($valueNode instanceof InvalidTagValueNode) {\n\t\t\t\t$multilineFixed = false;\n\t\t\t\tfor ($p = $i + 3; $p < $closeTagIndex; $p++) {\n\t\t\t\t\tif ($tokens[$p]['type'] === 'T_DOC_COMMENT_TAG') {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ($tokens[$p]['type'] !== 'T_DOC_COMMENT_STRING') {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t$content .= $tokens[$p]['content'];\n\t\t\t\t\t/** @var \\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\InvalidTagValueNode|\\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\ReturnTagValueNode $valueNode */\n\t\t\t\t\t$valueNode = static::getValueNode($tokens[$i]['content'], $content);\n\t\t\t\t\tif (!($valueNode instanceof InvalidTagValueNode)) {\n\t\t\t\t\t\t$multilineFixed = true;\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (!$multilineFixed || $valueNode instanceof InvalidTagValueNode) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$returnTypes = $this->valueNodeParts($valueNode);\n\t\t\t$typeString = $this->renderUnionTypes($returnTypes);\n\n\t\t\t$tag = $tokens[$i]['content'];\n\t\t\t$variablePos = strpos($content, ' $');\n\t\t\tif (in_array($tag, [VariableAnnotation::TAG, PropertyAnnotation::TAG]) && $variablePos) {\n\t\t\t\t$content = mb_substr($content, $variablePos + 1);\n\t\t\t} else {\n\t\t\t\t$content = mb_substr($content, mb_strlen($typeString) + 1);\n\t\t\t}\n\n\t\t\t$annotation = AnnotationFactory::createOrFail($tag, $typeString, $content, $classNameIndex);\n\t\t\tif ($this->getConfig(static::CONFIG_REMOVE) && $tag === VariableAnnotation::TAG && $this->varInUse($tokens, $closeTagIndex, $content)) {\n\t\t\t\t$annotation->setInUse();\n\t\t\t}\n\t\t\tif ($this->getConfig(static::CONFIG_REMOVE) && $tag === PropertyAnnotation::TAG && $this->propertyInUse($tokens, $closeTagIndex, $content)) {\n\t\t\t\t$annotation->setInUse();\n\t\t\t}\n\t\t\tif ($this->getConfig(static::CONFIG_REMOVE) && $tag === PropertyReadAnnotation::TAG && $this->propertyInUse($tokens, $closeTagIndex, $content)) {\n\t\t\t\t$annotation->setInUse();\n\t\t\t}\n\t\t\tif ($this->getConfig(static::CONFIG_REMOVE) && $tag === MethodAnnotation::TAG && $this->methodInUse($tokens, $closeTagIndex, $content)) {\n\t\t\t\t$annotation->setInUse();\n\t\t\t}\n\n\t\t\t$annotations[] = $annotation;\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * Checks the var token for existence.\n\t *\n\t * T_VARIABLE ..., content=`$variable`\n\t *\n\t * @param array<array<string, mixed>> $tokens\n\t * @param int $closeTagIndex\n\t * @param string $variable\n\t *\n\t * @return bool\n\t */\n\tprotected function varInUse(array $tokens, int $closeTagIndex, string $variable): bool {\n\t\tif ($variable === '$this') {\n\t\t\treturn false;\n\t\t}\n\n\t\t$i = $closeTagIndex + 1;\n\t\twhile (isset($tokens[$i])) {\n\t\t\tif ($tokens[$i]['code'] === T_VARIABLE && $tokens[$i]['content'] === $variable) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t$i++;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Checks the property token chain for existence.\n\t *\n\t * T_VARIABLE ..., content=`$this`\n\t * T_OBJECT_OPERATOR ..., content=`->`\n\t * T_STRING ..., content=`PropertyName`\n\t * T_OBJECT_OPERATOR ..., content=`->`\n\t *\n\t * @param array<array<string, mixed>> $tokens\n\t * @param int $closeTagIndex\n\t * @param string $variable\n\t *\n\t * @return bool\n\t */\n\tprotected function propertyInUse(array $tokens, int $closeTagIndex, string $variable): bool {\n\t\t$property = substr($variable, 1);\n\n\t\t$i = $closeTagIndex + 1;\n\t\twhile (isset($tokens[$i])) {\n\t\t\tif ($tokens[$i]['code'] !== T_VARIABLE || $tokens[$i]['content'] !== '$this') {\n\t\t\t\t$i++;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$i++;\n\t\t\tif (!isset($tokens[$i])) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ($tokens[$i]['code'] !== T_OBJECT_OPERATOR) {\n\t\t\t\t$i++;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$i++;\n\t\t\tif (!isset($tokens[$i])) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ($tokens[$i]['code'] !== T_STRING || $tokens[$i]['content'] !== $property) {\n\t\t\t\t$i++;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$i++;\n\t\t\tif (!isset($tokens[$i])) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ($tokens[$i]['code'] !== T_OBJECT_OPERATOR) {\n\t\t\t\t$i++;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Checks the property token chain for existence.\n\t *\n\t * T_VARIABLE ..., content=`$this`\n\t * T_OBJECT_OPERATOR ..., content=`->`\n\t * T_STRING ..., content=`method`\n\t * T_OPEN_PARENTHESIS ..., content=`(`\n\t *\n\t * @param array<array<string, mixed>> $tokens\n\t * @param int $closeTagIndex\n\t * @param string $method\n\t *\n\t * @return bool\n\t */\n\tprotected function methodInUse(array $tokens, int $closeTagIndex, string $method): bool {\n\t\tif (!preg_match('#^(\\w+)\\(#', $method, $matches)) {\n\t\t\treturn false;\n\t\t}\n\t\t$method = $matches[1];\n\n\t\t$i = $closeTagIndex + 1;\n\t\twhile (isset($tokens[$i])) {\n\t\t\tif ($tokens[$i]['code'] !== T_VARIABLE || $tokens[$i]['content'] !== '$this') {\n\t\t\t\t$i++;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$i++;\n\t\t\tif (!isset($tokens[$i])) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ($tokens[$i]['code'] !== T_OBJECT_OPERATOR) {\n\t\t\t\t$i++;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$i++;\n\t\t\tif (!isset($tokens[$i])) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ($tokens[$i]['code'] !== T_STRING || $tokens[$i]['content'] !== $method) {\n\t\t\t\t$i++;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$i++;\n\t\t\tif (!isset($tokens[$i])) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ($tokens[$i]['code'] !== T_OPEN_PARENTHESIS) {\n\t\t\t\t$i++;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $lastTagIndexOfPreviousLine\n\t *\n\t * @return bool\n\t */\n\tprotected function needsNewLineInDocBlock(File $file, int $lastTagIndexOfPreviousLine): bool {\n\t\t$tokens = $file->getTokens();\n\n\t\t$line = $tokens[$lastTagIndexOfPreviousLine]['line'];\n\t\t$index = $lastTagIndexOfPreviousLine - 1;\n\n\t\twhile ($tokens[$index]['line'] === $line) {\n\t\t\tif ($tokens[$index]['code'] === T_DOC_COMMENT_TAG || $tokens[$index]['code'] === T_DOC_COMMENT_OPEN_TAG) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t$index--;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation>|array<string> $annotations\n\t *\n\t * @return string\n\t */\n\tprotected function addNewDocBlock(File $file, int $index, array $annotations) {\n\t\t$tokens = $file->getTokens();\n\n\t\tforeach ($annotations as $key => $annotation) {\n\t\t\tif (is_string($annotation)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$annotations[$key] = (string)$annotation;\n\t\t}\n\n\t\t$helper = new DocBlockHelper(new View());\n\t\t$annotationString = $helper->classDescription('', '', $annotations);\n\t\tif (PHP_EOL !== \"\\n\") {\n\t\t\t$annotationString = str_replace(\"\\n\", PHP_EOL, $annotationString);\n\t\t}\n\n\t\t$fixer = $this->getFixer($file);\n\n\t\t$docBlock = $annotationString . PHP_EOL;\n\t\t$fixer->replaceToken($index, $docBlock . $tokens[$index]['content']);\n\n\t\t$contents = $fixer->getContents();\n\n\t\t$this->_counter[static::COUNT_ADDED] = count($annotations);\n\n\t\treturn $contents;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $docBlockCloseIndex\n\t *\n\t * @return bool\n\t */\n\tprotected function isInlineDocBlock(File $file, int $docBlockCloseIndex): bool {\n\t\t$tokens = $file->getTokens();\n\n\t\t$docBlockOpenIndex = $tokens[$docBlockCloseIndex]['comment_opener'];\n\n\t\treturn $tokens[$docBlockCloseIndex]['line'] === $tokens[$docBlockOpenIndex]['line'];\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $docBlockCloseIndex\n\t * @return bool\n\t */\n\tprotected function shouldSkip(File $file, int $docBlockCloseIndex): bool {\n\t\t$tokens = $file->getTokens();\n\t\t$docBlockOpenIndex = $tokens[$docBlockCloseIndex]['comment_opener'];\n\n\t\tfor ($i = $docBlockOpenIndex + 1; $i < $docBlockCloseIndex; $i++) {\n\t\t\tif ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (mb_strtolower($tokens[$i]['content']) === '@inheritdoc') {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param array<string> $usedModels\n\t * @param string $content\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function getModelAnnotations(array $usedModels, string $content): array {\n\t\t$annotations = [];\n\n\t\tforeach ($usedModels as $usedModel) {\n\t\t\t$className = App::className($usedModel, 'Model/Table', 'Table');\n\t\t\tif (!$className) {\n\t\t\t\t$className = 'Cake\\ORM\\Table';\n\t\t\t}\n\t\t\t[, $name] = pluginSplit($usedModel);\n\n\t\t\t$annotations[] = AnnotationFactory::createOrFail(PropertyAnnotation::TAG, '\\\\' . $className, '$' . $name);\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * Gets protected/private property of a class.\n\t *\n\t * So\n\t *   $this->invokeProperty($object, '_foo');\n\t * is equal to\n\t *   $object->_foo\n\t * (assuming the property was directly publicly accessible)\n\t *\n\t * @param object $object Instantiated object that we want the property off.\n\t * @param string $name Property name to fetch.\n\t *\n\t * @return mixed Property value.\n\t */\n\tprotected function invokeProperty(&$object, string $name) {\n\t\t$reflection = new ReflectionClass(get_class($object));\n\t\tif (!$reflection->hasProperty($name)) {\n\t\t\treturn null;\n\t\t}\n\t\t$property = $reflection->getProperty($name);\n\n\t\treturn $property->getValue($object);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function report(): void {\n\t\t$out = [];\n\n\t\t$added = !empty($this->_counter[static::COUNT_ADDED]) ? $this->_counter[static::COUNT_ADDED] : 0;\n\t\tif ($added) {\n\t\t\t$out[] = $added . ' ' . ($added === 1 ? 'annotation' : 'annotations') . ' added';\n\t\t}\n\t\t$updated = !empty($this->_counter[static::COUNT_UPDATED]) ? $this->_counter[static::COUNT_UPDATED] : 0;\n\t\tif ($updated) {\n\t\t\t$out[] = $updated . ' ' . ($updated === 1 ? 'annotation' : 'annotations') . ' updated';\n\t\t}\n\t\t$removed = !empty($this->_counter[static::COUNT_REMOVED]) ? $this->_counter[static::COUNT_REMOVED] : 0;\n\t\tif ($removed) {\n\t\t\t$out[] = $removed . ' ' . ($removed === 1 ? 'annotation' : 'annotations') . ' removed';\n\t\t}\n\t\t$skipped = !empty($this->_counter[static::COUNT_SKIPPED]) ? $this->_counter[static::COUNT_SKIPPED] : 0;\n\t\tif ($skipped) {\n\t\t\t$out[] = $skipped . ' ' . ($skipped === 1 ? 'annotation' : 'annotations') . ' skipped';\n\t\t}\n\n\t\tif (!$out) {\n\t\t\treturn;\n\t\t}\n\n\t\t$this->_io->success('   -> ' . implode(', ', $out) . '.');\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return void\n\t */\n\tprotected function reportSkipped(string $path): void {\n\t\t$out = [];\n\n\t\t$skipped = !empty($this->_counter[static::COUNT_SKIPPED]) ? $this->_counter[static::COUNT_SKIPPED] : 0;\n\t\tif ($skipped) {\n\t\t\t$out[] = $skipped . ' ' . ($skipped === 1 ? 'annotation' : 'annotations') . ' skipped';\n\t\t}\n\n\t\tif (!$out) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (!$this->getConfig('verbose')) {\n\t\t\t$this->_io->out('-> ' . str_replace(ROOT . DS, '', $path));\n\t\t}\n\n\t\t$this->_io->out('   -> ' . implode(', ', $out));\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function resetCounter(): void {\n\t\t$this->_counter = [\n\t\t\tstatic::COUNT_ADDED => 0,\n\t\t\tstatic::COUNT_UPDATED => 0,\n\t\t\tstatic::COUNT_REMOVED => 0,\n\t\t\tstatic::COUNT_SKIPPED => 0,\n\t\t];\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $classOrTraitIndex\n\t *\n\t * @return int\n\t */\n\tprotected function beginningOfLine(File $file, $classOrTraitIndex) {\n\t\t$tokens = $file->getTokens();\n\n\t\t$line = $tokens[$classOrTraitIndex]['line'];\n\t\t$beginningOfLineIndex = $classOrTraitIndex;\n\t\twhile ($tokens[$beginningOfLineIndex - 1]['line'] === $line) {\n\t\t\t$beginningOfLineIndex--;\n\t\t}\n\n\t\treturn $beginningOfLineIndex;\n\t}\n\n\t/**\n\t * @param class-string<object> $className\n\t *\n\t * @return bool\n\t */\n\tprotected function _isAbstract($className) {\n\t\t$reflection = new ReflectionClass($className);\n\n\t\treturn $reflection->isAbstract();\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t *\n\t * @return int\n\t */\n\tprotected function skipOverAttributes(File $file, int $index): int {\n\t\t$prevCode = $file->findPrevious(Tokens::$emptyTokens, $index - 1, null, true);\n\t\tif (!$prevCode) {\n\t\t\treturn $index;\n\t\t}\n\n\t\t$tokens = $file->getTokens();\n\t\twhile ($prevCode && $tokens[$prevCode]['code'] === T_ATTRIBUTE_END) {\n\t\t\t$beginningOfLine = $this->beginningOfLine($file, $prevCode);\n\t\t\t$prevCode = $file->findPrevious(Tokens::$emptyTokens, $beginningOfLine - 1, null, true);\n\t\t}\n\n\t\treturn $beginningOfLine ?? $index;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/CallbackAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse RuntimeException;\n\nclass CallbackAnnotator extends AbstractAnnotator {\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\n\t\t$this->invokeTasks($path, $content);\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t *\n\t * @return void\n\t */\n\tprotected function invokeTasks(string $path, string $content): void {\n\t\t$tasks = $this->getTasks($path, $content);\n\n\t\tforeach ($tasks as $task) {\n\t\t\tif (!$task->shouldRun($path)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$task->annotate($path);\n\t\t}\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t * @return array<\\IdeHelper\\Annotator\\CallbackAnnotatorTask\\CallbackAnnotatorTaskInterface>\n\t */\n\tprotected function getTasks(string $path, string $content): array {\n\t\t$taskCollection = new CallbackAnnotatorTaskCollection();\n\n\t\treturn $taskCollection->tasks($this->_io, $this->_config, $path, $content);\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/CallbackAnnotatorTask/AbstractCallbackAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\CallbackAnnotatorTask;\n\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Console\\Io;\nuse PHP_CodeSniffer\\Files\\File;\nuse PHP_CodeSniffer\\Util\\Tokens;\n\nabstract class AbstractCallbackAnnotatorTask extends AbstractAnnotator {\n\n\tprotected string $path;\n\n\tprotected string $content;\n\n\t/**\n\t * @param \\IdeHelper\\Console\\Io $io\n\t * @param array<string, mixed> $config\n\t * @param string $path\n\t * @param string $content\n\t */\n\tpublic function __construct(Io $io, array $config, $path, $content) {\n\t\tparent::__construct($io, $config);\n\n\t\t$this->path = $path;\n\t\t$this->content = $content;\n\t}\n\n\t/**\n\t * For testing only\n\t *\n\t * @return string\n\t */\n\tpublic function getContent(): string {\n\t\treturn $this->content;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t *\n\t * @return array<array<string, mixed>>\n\t */\n\tprotected function getMethods(File $file) {\n\t\t$methods = [];\n\t\t$currentIndex = 0;\n\t\twhile (true) {\n\t\t\t$methodIndex = $file->findNext(T_FUNCTION, $currentIndex);\n\t\t\tif (!$methodIndex) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t$methods[$methodIndex] = $this->parseMethod($file, $methodIndex);\n\n\t\t\t$currentIndex = $methodIndex + 1;\n\t\t}\n\n\t\treturn $methods;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t * @return array<string, mixed>\n\t */\n\tprotected function parseMethod(File $file, int $index) {\n\t\t$tokens = $file->getTokens();\n\t\t$nameIndex = $file->findNext(Tokens::$emptyTokens, $index + 1, null, true);\n\n\t\t$closeTagIndex = $this->findCloseTagIndex($file, $index);\n\n\t\t$result = [\n\t\t\t'name' => $tokens[$nameIndex]['content'],\n\t\t\t'docBlockStart' => $closeTagIndex ? $tokens[$closeTagIndex]['comment_opener'] : null,\n\t\t\t'docBlockEnd' => $closeTagIndex ?: null,\n\t\t];\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<array<string, mixed>> $methods\n\t * @return bool\n\t */\n\tprotected function annotateMethods(string $path, File $file, array $methods): bool {\n\t\t$this->resetCounter();\n\n\t\t$fixer = $this->getFixer($file);\n\n\t\t$fixer->beginChangeset();\n\n\t\tforeach ($methods as $method) {\n\t\t\t/** @var array<\\IdeHelper\\Annotation\\ParamAnnotation> $replacingAnnotations */\n\t\t\t$replacingAnnotations = $method['annotations'];\n\t\t\tforeach ($replacingAnnotations as $annotation) {\n\t\t\t\t$fixer->replaceToken($annotation->getIndex(), $annotation->build());\n\t\t\t\t$this->_counter[static::COUNT_UPDATED]++;\n\t\t\t}\n\t\t}\n\n\t\t$fixer->endChangeset();\n\n\t\t$newContent = $fixer->getContents();\n\n\t\tif ($newContent === $this->content) {\n\t\t\t$this->reportSkipped($path);\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$this->displayDiff($this->content, $newContent);\n\t\t$this->storeFile($path, $newContent);\n\t\t$this->content = $newContent;\n\n\t\t$this->report();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t * @return int|null\n\t */\n\tprotected function findCloseTagIndex(File $file, int $index): ?int {\n\t\t$tokens = $file->getTokens();\n\n\t\t$beginningOfLineIndex = $index;\n\t\twhile ($tokens[$beginningOfLineIndex - 1]['line'] === $tokens[$index]['line']) {\n\t\t\t$beginningOfLineIndex--;\n\t\t}\n\n\t\t$prevCodeIndex = $file->findPrevious(Tokens::$emptyTokens, $beginningOfLineIndex - 1, null, true);\n\t\t$closeTagIndex = $file->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $index - 1, $prevCodeIndex ?: null);\n\n\t\treturn $closeTagIndex ?: null;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/CallbackAnnotatorTask/CallbackAnnotatorTaskInterface.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\CallbackAnnotatorTask;\n\ninterface CallbackAnnotatorTaskInterface {\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path): bool;\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool;\n\n}\n"
  },
  {
    "path": "src/Annotator/CallbackAnnotatorTask/TableCallbackAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\CallbackAnnotatorTask;\n\nuse Cake\\ORM\\TableRegistry;\nuse IdeHelper\\Annotation\\ParamAnnotation;\nuse PHP_CodeSniffer\\Files\\File;\nuse Throwable;\n\n/**\n * Fix up generic *(EventInterface $event, EntityInterface $entity, ...) hook methods to have at least the $entity documented as concrete class.\n */\nclass TableCallbackAnnotatorTask extends AbstractCallbackAnnotatorTask implements CallbackAnnotatorTaskInterface {\n\n\t/**\n\t * @var array<string, string>\n\t */\n\tprotected array $callbacks = [\n\t\t'afterMarshal' => 'afterMarshal',\n\t\t'beforeRules' => 'beforeRules',\n\t\t'afterRules' => 'afterRules',\n\t\t'beforeSave' => 'beforeSave',\n\t\t'afterSave' => 'afterSave',\n\t\t'afterSaveCommit' => 'afterSaveCommit',\n\t\t'beforeDelete' => 'beforeDelete',\n\t\t'afterDelete' => 'afterDelete',\n\t\t'afterDeleteCommit' => 'afterDeleteCommit',\n\t];\n\n\t/**\n\t * @var string|null\n\t */\n\tprotected $entityClassName;\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path): bool {\n\t\t$className = pathinfo($path, PATHINFO_FILENAME);\n\t\tif ($className === 'Table' || !str_ends_with($className, 'Table')) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$modelName = substr($className, 0, -5);\n\t\t$plugin = $this->getConfig(static::CONFIG_PLUGIN);\n\n\t\ttry {\n\t\t\t$table = TableRegistry::getTableLocator()->get($plugin ? ($plugin . '.' . $modelName) : $modelName);\n\t\t} catch (Throwable $e) {\n\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t$this->_io->warn('   Skipping table: ' . $e->getMessage());\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$entityClassName = $table->getEntityClass();\n\t\t$this->entityClassName = $entityClassName;\n\n\t\tif (!preg_match('#\\bfunction (' . $this->generatePattern() . ')\\b#', $this->content)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$file = $this->getFile($path, $this->content);\n\n\t\t$methods = $this->getMethods($file);\n\n\t\tforeach ($methods as $index => $method) {\n\t\t\tif (!in_array($method['name'], $this->callbacks, true)) {\n\t\t\t\tunset($methods[$index]);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!$this->needsUpdate($file, $index, $method)) {\n\t\t\t\tunset($methods[$index]);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$methods[$index] = $method;\n\t\t}\n\n\t\treturn $this->annotateMethods($path, $file, $methods);\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t * @param array<string, mixed> $method\n\t * @return bool\n\t */\n\tprotected function needsUpdate(File $file, int $index, array &$method): bool {\n\t\tif (empty($method['docBlockStart']) || empty($method['docBlockEnd'])) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$annotations = $this->parseExistingAnnotations($file, $method['docBlockEnd'], ['@param']);\n\n\t\tif (empty($annotations[1])) {\n\t\t\treturn false;\n\t\t}\n\n\t\t/** @var \\IdeHelper\\Annotation\\ParamAnnotation $currentAnnotation */\n\t\t$currentAnnotation = $annotations[1];\n\t\t$expectedAnnotation = new ParamAnnotation('\\\\' . $this->entityClassName, $currentAnnotation->getVariable());\n\n\t\tif ($currentAnnotation->getType() === $expectedAnnotation->getType()) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$currentAnnotation->replaceWith($expectedAnnotation);\n\n\t\t$method['annotations'] = [\n\t\t\t$currentAnnotation,\n\t\t];\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tprotected function generatePattern(): string {\n\t\t$pattern = [];\n\t\tforeach ($this->callbacks as $key => $v) {\n\t\t\t$pattern[] = preg_quote($key . '(');\n\t\t}\n\n\t\treturn implode('|', $pattern);\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/CallbackAnnotatorTask/VirtualFieldCallbackAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\CallbackAnnotatorTask;\n\nuse Cake\\Core\\Exception\\CakeException;\nuse Cake\\Utility\\Inflector;\nuse IdeHelper\\Annotation\\SeeAnnotation;\nuse PHP_CodeSniffer\\Files\\File;\n\n/**\n * Fix up entity virtual field methods to have at least the $property linked.\n */\nclass VirtualFieldCallbackAnnotatorTask extends AbstractCallbackAnnotatorTask implements CallbackAnnotatorTaskInterface {\n\n\t/**\n\t * @var array<string, string>\n\t */\n\tprotected array $methods = [\n\t\t'_get' => '#_get[A-Z]\\w+#',\n\t\t'_set' => '#_set[A-Z]\\w+#',\n\t];\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path): bool {\n\t\tif (!preg_match('#/Model/Entity/\\w+\\.php$#', $path)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$file = $this->getFile($path, $this->content);\n\n\t\t$methods = $this->getMethods($file);\n\t\t$namespace = $this->getNamespace($file);\n\t\t$class = $this->getClass($file);\n\n\t\tforeach ($methods as $index => $method) {\n\t\t\tif (!$this->isVirtualField($method)) {\n\t\t\t\tunset($methods[$index]);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$method['see'] = $namespace . '\\\\' . $class . '::$' . Inflector::underscore(substr($method['name'], 4));\n\n\t\t\tif (!$this->needsUpdate($file, $index, $method)) {\n\t\t\t\tunset($methods[$index]);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$methods[$index] = $method;\n\t\t}\n\n\t\tif (!$methods) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn $this->annotateMethods($path, $file, $methods);\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<array<string, mixed>> $methods\n\t * @return bool\n\t */\n\tprotected function annotateMethods(string $path, File $file, array $methods): bool {\n\t\t$this->resetCounter();\n\n\t\t$fixer = $this->getFixer($file);\n\n\t\t$fixer->beginChangeset();\n\n\t\tforeach ($methods as $method) {\n\t\t\t/** @var array<\\IdeHelper\\Annotation\\ParamAnnotation> $replacingAnnotations */\n\t\t\t$replacingAnnotations = $method['annotations'] ?? [];\n\t\t\tforeach ($replacingAnnotations as $annotation) {\n\t\t\t\t$fixer->replaceToken($annotation->getIndex(), $annotation->build());\n\t\t\t\t$this->_counter[static::COUNT_UPDATED]++;\n\t\t\t}\n\n\t\t\t/** @var \\IdeHelper\\Annotation\\LinkAnnotation|null $addingAnnotation */\n\t\t\t$addingAnnotation = $method['annotation'] ?? null;\n\t\t\tif ($addingAnnotation) {\n\t\t\t\t$endIndex = $method['docBlockEnd'];\n\n\t\t\t\t$indentation = $this->indentation($file, $endIndex);\n\t\t\t\t$fixer->addContentBefore($endIndex, '* @see ' . $addingAnnotation->build() . PHP_EOL . $indentation);\n\t\t\t}\n\t\t}\n\n\t\t$fixer->endChangeset();\n\n\t\t$newContent = $fixer->getContents();\n\n\t\tif ($newContent === $this->content) {\n\t\t\t$this->reportSkipped($path);\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$this->displayDiff($this->content, $newContent);\n\t\t$this->storeFile($path, $newContent);\n\t\t$this->content = $newContent;\n\n\t\t$this->report();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t * @param array<string, mixed> $method\n\t * @return bool\n\t */\n\tprotected function needsUpdate(File $file, int $index, array &$method): bool {\n\t\tif (empty($method['docBlockStart']) || empty($method['docBlockEnd'])) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$annotations = $this->parseExistingAnnotations($file, $method['docBlockEnd'], ['@see']);\n\n\t\t$expectedAnnotation = new SeeAnnotation($method['see']);\n\t\tif (!$annotations || $annotations[0]->getType() !== $method['see']) {\n\t\t\t$method['annotation'] = $expectedAnnotation;\n\n\t\t\treturn true;\n\t\t}\n\n\t\t/** @var \\IdeHelper\\Annotation\\LinkAnnotation $currentAnnotation */\n\t\t$currentAnnotation = $annotations[0];\n\t\tif ($currentAnnotation->getType() === $expectedAnnotation->getType()) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$currentAnnotation->replaceWith($expectedAnnotation);\n\t\t$currentAnnotation->setInUse();\n\n\t\t$method['annotations'] = [\n\t\t\t$currentAnnotation,\n\t\t];\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param array<string, mixed> $method\n\t *\n\t * @return bool\n\t */\n\tprotected function isVirtualField(array $method): bool {\n\t\tforeach ($this->methods as $regex) {\n\t\t\tif (preg_match($regex, $method['name'])) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t *\n\t * @return string\n\t */\n\tprotected function getNamespace(File $file): string {\n\t\t$namespaceIndex = $file->findNext(T_NAMESPACE, 0);\n\t\t$startIndex = $file->findNext(T_WHITESPACE, $namespaceIndex + 1, null, true);\n\t\t$endIndex = $file->findNext(T_SEMICOLON, $namespaceIndex + 1);\n\n\t\tif (!$namespaceIndex || !$startIndex || !$endIndex) {\n\t\t\tthrow new CakeException('File does not seem to be a valid entity class');\n\t\t}\n\n\t\t$tokens = $file->getTokens();\n\t\t$elements = ['\\\\'];\n\t\tfor ($i = $startIndex; $i < $endIndex; $i++) {\n\t\t\t$elements[] = $tokens[$i]['content'];\n\t\t}\n\n\t\treturn implode('', $elements);\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t *\n\t * @return string\n\t */\n\tprotected function getClass(File $file): string {\n\t\t$classIndex = $file->findNext(T_CLASS, 0);\n\t\t$classNameIndex = $file->findNext(T_WHITESPACE, $classIndex + 1, null, true);\n\n\t\t$tokens = $file->getTokens();\n\t\tif (!$classNameIndex || empty($tokens[$classNameIndex])) {\n\t\t\tthrow new CakeException('File does not seem to be a valid entity class');\n\t\t}\n\n\t\treturn $tokens[$classNameIndex]['content'];\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $endIndex\n\t *\n\t * @return string\n\t */\n\tprotected function indentation(File $file, int $endIndex): string {\n\t\t$tokens = $file->getTokens();\n\t\t$indentationElements = [];\n\t\tfor ($i = $endIndex - 1; $i > 0; $i--) {\n\t\t\tif ($tokens[$i]['line'] !== $tokens[$endIndex]['line']) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t$indentationElements[] = $tokens[$i]['content'];\n\t\t}\n\n\t\treturn implode('', $indentationElements);\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/CallbackAnnotatorTaskCollection.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotator\\CallbackAnnotatorTask\\TableCallbackAnnotatorTask;\nuse IdeHelper\\Annotator\\CallbackAnnotatorTask\\VirtualFieldCallbackAnnotatorTask;\nuse IdeHelper\\Console\\Io;\n\nclass CallbackAnnotatorTaskCollection {\n\n\t/**\n\t * @var array<class-string<\\IdeHelper\\Annotator\\CallbackAnnotatorTask\\CallbackAnnotatorTaskInterface>, class-string<\\IdeHelper\\Annotator\\CallbackAnnotatorTask\\CallbackAnnotatorTaskInterface>>\n\t */\n\tprotected array $defaultTasks = [\n\t\tTableCallbackAnnotatorTask::class => TableCallbackAnnotatorTask::class,\n\t\tVirtualFieldCallbackAnnotatorTask::class => VirtualFieldCallbackAnnotatorTask::class,\n\t];\n\n\t/**\n\t * @var array<class-string<\\IdeHelper\\Annotator\\CallbackAnnotatorTask\\CallbackAnnotatorTaskInterface>>\n\t */\n\tprotected array $tasks;\n\n\t/**\n\t * @param array<class-string<\\IdeHelper\\Annotator\\CallbackAnnotatorTask\\CallbackAnnotatorTaskInterface>> $tasks\n\t */\n\tpublic function __construct(array $tasks = []) {\n\t\t$defaultTasks = $this->defaultTasks();\n\t\t$tasks += $defaultTasks;\n\n\t\tforeach ($tasks as $task) {\n\t\t\tif (!$task) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$this->tasks = $tasks;\n\t\t}\n\t}\n\n\t/**\n\t * @return array<class-string<\\IdeHelper\\Annotator\\CallbackAnnotatorTask\\CallbackAnnotatorTaskInterface>>\n\t */\n\tpublic function defaultTasks(): array {\n\t\t$tasks = (array)Configure::read('IdeHelper.callbackAnnotatorTasks') + $this->defaultTasks;\n\n\t\tforeach ($tasks as $k => $v) {\n\t\t\tif (is_numeric($k)) {\n\t\t\t\t$tasks[$v] = $v;\n\t\t\t\tunset($tasks[$k]);\n\t\t\t}\n\t\t}\n\n\t\treturn $tasks;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Console\\Io $io\n\t * @param array<string, mixed> $config\n\t * @param string $path\n\t * @param string $content\n\t * @return array<\\IdeHelper\\Annotator\\CallbackAnnotatorTask\\CallbackAnnotatorTaskInterface>\n\t */\n\tpublic function tasks(Io $io, array $config, string $path, string $content): array {\n\t\t$tasks = $this->tasks;\n\n\t\t$collection = [];\n\t\tforeach ($tasks as $task) {\n\t\t\t/** @var \\IdeHelper\\Annotator\\CallbackAnnotatorTask\\CallbackAnnotatorTaskInterface $object */\n\t\t\t$object = new $task($io, $config, $path, $content);\n\t\t\t$collection[] = $object;\n\t\t}\n\n\t\treturn $collection;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse RuntimeException;\n\nclass ClassAnnotator extends AbstractAnnotator {\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\n\t\t$this->invokeTasks($path, $content);\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t *\n\t * @return void\n\t */\n\tprotected function invokeTasks(string $path, string $content): void {\n\t\t$tasks = $this->getTasks($content);\n\n\t\tforeach ($tasks as $task) {\n\t\t\tif (!$task->shouldRun($path, $content)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$task->annotate($path);\n\t\t}\n\t}\n\n\t/**\n\t * @param string $content\n\t * @return array<\\IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface>\n\t */\n\tprotected function getTasks(string $content): array {\n\t\t$taskCollection = new ClassAnnotatorTaskCollection();\n\n\t\treturn $taskCollection->tasks($this->_io, $this->_config, $content);\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTask/AbstractClassAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\ClassAnnotatorTask;\n\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Console\\Io;\nuse PHP_CodeSniffer\\Files\\File;\nuse PHP_CodeSniffer\\Util\\Tokens;\nuse RuntimeException;\n\nabstract class AbstractClassAnnotatorTask extends AbstractAnnotator {\n\n\tprotected string $content;\n\n\t/**\n\t * @param \\IdeHelper\\Console\\Io $io\n\t * @param array<string, mixed> $config\n\t * @param string $content\n\t */\n\tpublic function __construct(Io $io, array $config, $content) {\n\t\tparent::__construct($io, $config);\n\n\t\t$this->content = $content;\n\t}\n\n\t/**\n\t * For testing only\n\t *\n\t * @return string\n\t */\n\tpublic function getContent(): string {\n\t\treturn $this->content;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $annotations\n\t *\n\t * @return bool\n\t */\n\tprotected function annotateContent(string $path, string $content, array $annotations): bool {\n\t\tif (!count($annotations)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$file = $this->getFile($path, $content);\n\n\t\t$classOrTraitIndex = $file->findNext([T_CLASS, T_TRAIT], 0);\n\t\tif (!$classOrTraitIndex) {\n\t\t\treturn false;\n\t\t}\n\t\t$beginningOfLineIndex = $this->beginningOfLine($file, $classOrTraitIndex);\n\n\t\t$attributeTokens = [\n\t\t\tT_ATTRIBUTE => T_ATTRIBUTE,\n\t\t\tT_ATTRIBUTE_END => T_ATTRIBUTE_END,\n\t\t\tT_NS_SEPARATOR => T_NS_SEPARATOR,\n\t\t\tT_STRING => T_STRING,\n\t\t];\n\t\t$prevCode = $file->findPrevious(Tokens::$emptyTokens + $attributeTokens, $beginningOfLineIndex - 1, null, true);\n\t\tif ($prevCode === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$closeTagIndex = $file->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $beginningOfLineIndex - 1, $prevCode);\n\t\t$this->resetCounter();\n\t\tif ($closeTagIndex && $this->shouldSkip($file, $closeTagIndex)) {\n\t\t\treturn false;\n\t\t}\n\t\tif ($closeTagIndex && !$this->isInlineDocBlock($file, $closeTagIndex)) {\n\t\t\t$newContent = $this->appendToExistingDocBlock($file, $closeTagIndex, $annotations);\n\t\t} else {\n\t\t\t$newContent = $this->addNewDocBlock($file, $beginningOfLineIndex, $annotations);\n\t\t}\n\n\t\tif ($newContent === $content) {\n\t\t\t$this->reportSkipped($path);\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$this->displayDiff($content, $newContent);\n\t\t$this->storeFile($path, $newContent);\n\t\t$this->content = $newContent;\n\n\t\t$this->report();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $annotations\n\t * @param int $line\n\t *\n\t * @return bool\n\t */\n\tprotected function annotateInlineContent(string $path, string $content, array $annotations, int $line): bool {\n\t\tif (!count($annotations)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$file = $this->getFile($path, $content);\n\n\t\t$beginningOfLineIndex = $this->findFirstTokenOfLine($file, $line);\n\t\tif (!$beginningOfLineIndex) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$prevCode = $file->findPrevious(Tokens::$emptyTokens, $beginningOfLineIndex - 1, null, true);\n\t\tif ($prevCode === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$closeTagIndex = $file->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $beginningOfLineIndex - 1, $prevCode);\n\t\t$this->resetCounter();\n\t\tif ($closeTagIndex && $this->shouldSkip($file, $closeTagIndex)) {\n\t\t\treturn false;\n\t\t}\n\t\tif ($closeTagIndex) {\n\t\t\t// Skip as there seems to be already one\n\t\t\t$newContent = $content;\n\t\t} else {\n\t\t\t$newContent = $this->addNewInlineDocBlock($file, $beginningOfLineIndex, $annotations);\n\t\t}\n\n\t\tif ($newContent === $content) {\n\t\t\t$this->reportSkipped($path);\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$this->displayDiff($content, $newContent);\n\t\t$this->storeFile($path, $newContent);\n\t\t$this->content = $newContent;\n\n\t\t$this->report();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation>|array<string> $annotations\n\t *\n\t * @return string\n\t */\n\tprotected function addNewInlineDocBlock(File $file, int $index, array $annotations) {\n\t\t$tokens = $file->getTokens();\n\n\t\tif (count($annotations) !== 1) {\n\t\t\tthrow new RuntimeException('Cannot work with annotation count != 1 right now');\n\t\t}\n\n\t\t$annotation = reset($annotations);\n\t\t$annotationString = '/** ' . (string)$annotation . ' */';\n\t\tif (PHP_EOL !== \"\\n\") {\n\t\t\t$annotationString = str_replace(\"\\n\", PHP_EOL, $annotationString);\n\t\t}\n\t\t$indentation = $this->detectIndentation($file, $index);\n\n\t\t$fixer = $this->getFixer($file);\n\n\t\t$docBlock = $indentation . $annotationString . PHP_EOL;\n\t\t$fixer->replaceToken($index, $docBlock . $tokens[$index]['content']);\n\n\t\t$contents = $fixer->getContents();\n\n\t\t$this->_counter[static::COUNT_ADDED] = count($annotations);\n\n\t\treturn $contents;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $line\n\t *\n\t * @return int|null\n\t */\n\tprotected function findFirstTokenOfLine(File $file, int $line): ?int {\n\t\t$tokens = $file->getTokens();\n\t\tforeach ($tokens as $index => $token) {\n\t\t\tif ($token['line'] === $line) {\n\t\t\t\treturn $index;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t *\n\t * @return string\n\t */\n\tprotected function detectIndentation(File $file, int $index): string {\n\t\t$nextIndex = $file->findNext(T_WHITESPACE, $index + 1, null, true);\n\t\tif (!$nextIndex) {\n\t\t\treturn '';\n\t\t}\n\n\t\t$tokens = $file->getTokens();\n\t\t$whitespace = '';\n\t\tfor ($i = $index; $i < $nextIndex; $i++) {\n\t\t\t$whitespace .= $tokens[$i]['content'];\n\t\t}\n\n\t\treturn $whitespace;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTask/ClassAnnotatorTaskInterface.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\ClassAnnotatorTask;\n\ninterface ClassAnnotatorTaskInterface {\n\n\t/**\n\t * Deprecated: $content, use $this->content instead.\n\t *\n\t * @param string $path\n\t * @param string $content\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path, string $content): bool;\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool;\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTask/FormClassAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\UsesAnnotation;\n\n/**\n * Form classes should automatically have `@uses` annotated for method invocation.\n */\nclass FormClassAnnotatorTask extends AbstractClassAnnotatorTask implements ClassAnnotatorTaskInterface {\n\n\t/**\n\t * Deprecated: $content, use $this->content instead.\n\t *\n\t * @param string $path\n\t * @param string $content\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path, string $content): bool {\n\t\tif (!str_contains($path, DS . 'src' . DS)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$appNamespace = Configure::read('App.namespace') ?: 'App';\n\t\tif (!preg_match('#\\buse (\\w+)\\\\\\\\Form\\\\\\\\(.+)Form\\b#', $content, $matches)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$varName = lcfirst($matches[2]) . 'Form';\n\t\tif (!preg_match('#\\$' . preg_quote($varName, '#') . '->execute\\(#', $content)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\tpreg_match('#\\buse (\\w+)\\\\\\\\Form\\\\\\\\(.+)Form\\b#', $this->content, $matches);\n\t\tif (empty($matches[1]) || empty($matches[2])) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$appNamespace = $matches[1];\n\t\t$name = $matches[2] . 'Form';\n\n\t\t$varName = lcfirst($name);\n\t\t$rows = explode(PHP_EOL, $this->content);\n\t\t$rowToAnnotate = null;\n\t\tforeach ($rows as $i => $row) {\n\t\t\tif (!preg_match('#\\$' . preg_quote($varName, '#') . '->execute\\(#', $row)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$rowToAnnotate = $i + 1;\n\n\t\t\tbreak;\n\t\t}\n\n\t\tif (!$rowToAnnotate) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$method = $appNamespace . '\\\\Form\\\\' . $name . '::_execute()';\n\t\t$annotations = $this->buildUsesAnnotations([$method]);\n\n\t\treturn $this->annotateInlineContent($path, $this->content, $annotations, $rowToAnnotate);\n\t}\n\n\t/**\n\t * @param array<string> $classes\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function buildUsesAnnotations(array $classes): array {\n\t\t$annotations = [];\n\n\t\tforeach ($classes as $className) {\n\t\t\t$annotations[] = AnnotationFactory::createOrFail(UsesAnnotation::TAG, '\\\\' . $className);\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTask/MailerClassAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\UsesAnnotation;\n\n/**\n * Mailer classes should automatically have `@uses` annotated for method invocation.\n */\nclass MailerClassAnnotatorTask extends AbstractClassAnnotatorTask implements ClassAnnotatorTaskInterface {\n\n\t/**\n\t * Deprecated: $content, use $this->content instead.\n\t *\n\t * @param string $path\n\t * @param string $content\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path, string $content): bool {\n\t\tif (!str_contains($path, DS . 'src' . DS)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (preg_match('#namespace \\w+\\\\\\\\Mailer;#', $content)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tpreg_match('#\\buse (\\w+)\\\\\\\\Mailer\\\\\\\\(\\w+)Mailer\\b#', $content, $useMatches);\n\t\tpreg_match('#\\$\\w+\\s*=\\s*\\$this-\\>getMailer\\(\\'([\\w\\.]+)\\'\\)#', $content, $callMatches);\n\t\t$singleLine = false;\n\t\tif (!$callMatches) {\n\t\t\t$singleLine = true;\n\t\t\tpreg_match('#\\$this->getMailer\\(\\s*\\'([\\w\\.]+)\\'\\s*\\)\\s*->\\s*send\\(\\s*\\'([\\w\\.]+)\\'#msu', $content, $callMatches);\n\t\t}\n\t\tif (!$useMatches && !$callMatches) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($useMatches) {\n\t\t\t$varName = lcfirst($useMatches[2]) . 'Mailer';\n\t\t} else {\n\t\t\t$class = $callMatches[1];\n\t\t\t[$plugin, $name] = pluginSplit($class);\n\t\t\t$varName = lcfirst($name) . 'Mailer';\n\t\t}\n\n\t\tif ($singleLine && !empty($callMatches)) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (!preg_match('#\\$' . preg_quote($varName, '#') . '->send\\(\\'\\w+\\'#', $content)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\tpreg_match('#\\buse (\\w+)\\\\\\\\Mailer\\\\\\\\(\\w+)Mailer\\b#', $this->content, $useMatches);\n\n\t\t$singleCall = false;\n\t\t$singleCallAction = null;\n\t\tif (!$useMatches) {\n\t\t\tpreg_match('#\\$\\w+\\s*=\\s*\\$this->getMailer\\(\\'([\\w.]+)\\'\\)#', $this->content, $callMatches);\n\t\t\tif (!$callMatches) {\n\t\t\t\tpreg_match('#\\$this->getMailer\\(\\s*\\'([\\w\\.]+)\\'\\s*\\)\\s*->\\s*send\\(\\s*\\'([\\w\\.]+)\\'#msu', $this->content, $callMatches);\n\t\t\t\tif (!$callMatches) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\t$singleCall = true;\n\t\t\t\t$singleCallAction = $callMatches[2];\n\t\t\t}\n\t\t}\n\n\t\tif ($useMatches) {\n\t\t\t$appNamespace = $useMatches[1];\n\t\t\t$name = $useMatches[2] . 'Mailer';\n\t\t} else {\n\t\t\t[$plugin, $name] = pluginSplit($callMatches[1]);\n\t\t\t$appNamespace = $plugin ?: (Configure::read('App.namespace') ?: 'App');\n\t\t\t$name = $name . 'Mailer';\n\t\t}\n\n\t\t$action = null;\n\t\tif (!$singleCall) {\n\t\t\t$varName = lcfirst($name);\n\t\t\t$rows = explode(PHP_EOL, $this->content);\n\t\t\t$rowToAnnotate = null;\n\t\t\t$rowMatches = null;\n\t\t\tforeach ($rows as $i => $row) {\n\t\t\t\tif (!preg_match('#\\$' . preg_quote($varName, '#') . '->send\\(\\'(\\w+)\\'#', $row, $rowMatches)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t$rowToAnnotate = $i + 1;\n\t\t\t\t$action = $rowMatches[1];\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tassert($singleCallAction !== null);\n\t\t\t$rows = explode(PHP_EOL, $this->content);\n\t\t\t$rowToAnnotate = null;\n\t\t\t$rowMatches = null;\n\t\t\t$multiLine = str_contains($callMatches[0], PHP_EOL);\n\t\t\tforeach ($rows as $i => $row) {\n\t\t\t\tif (\n\t\t\t\t\t$multiLine\n\t\t\t\t\t&& preg_match('#\\$this->getMailer\\(\\s*\\'' . $callMatches[1] . '\\'\\s*\\)#msu', $row, $rowMatches)\n\t\t\t\t\t&& !empty($rows[$i + 1])\n\t\t\t\t\t&& preg_match('#->\\s*send\\(\\s*\\'' . $singleCallAction . '\\'#msu', $rows[$i + 1], $rowMatches)\n\t\t\t\t) {\n\t\t\t\t\t$rowToAnnotate = $i;\n\t\t\t\t\t$action = $singleCallAction;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif (!preg_match('#\\$this->getMailer\\(\\s*\\'' . $callMatches[1] . '\\'\\s*\\)\\s*->\\s*send\\(\\s*\\'' . $singleCallAction . '\\'#msu', $row, $rowMatches)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t$rowToAnnotate = $i + 1;\n\t\t\t\t$action = $singleCallAction;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (!$rowToAnnotate) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$method = $appNamespace . '\\\\Mailer\\\\' . $name . '::' . $action . '()';\n\t\t$annotations = $this->buildUsesAnnotations([$method]);\n\n\t\treturn $this->annotateInlineContent($path, $this->content, $annotations, $rowToAnnotate);\n\t}\n\n\t/**\n\t * @param array<string> $classes\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function buildUsesAnnotations(array $classes): array {\n\t\t$annotations = [];\n\n\t\tforeach ($classes as $className) {\n\t\t\t$annotations[] = AnnotationFactory::createOrFail(UsesAnnotation::TAG, '\\\\' . $className);\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTask/ModelAwareClassAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\ClassAnnotatorTask;\n\nuse ReflectionClass;\nuse Throwable;\n\n/**\n * Classes that use ModelAwareTrait should automatically have used tables - via fetchModel() call - annotated.\n */\nclass ModelAwareClassAnnotatorTask extends AbstractClassAnnotatorTask implements ClassAnnotatorTaskInterface {\n\n\t/**\n\t * Deprecated: $content, use $this->content instead.\n\t *\n\t * @param string $path\n\t * @param string $content\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path, string $content): bool {\n\t\tif (!str_contains($path, DS . 'src' . DS)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (preg_match('#\\buse ModelAwareTrait\\b#', $content)) {\n\t\t\treturn true;\n\t\t}\n\n\t\t/** @var class-string|null $className */\n\t\t$className = $this->getClassName($path, $content);\n\t\tif (!$className) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\treturn (new ReflectionClass($className))->hasMethod('fetchModel');\n\t\t} catch (Throwable $exception) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$models = $this->getUsedModels($this->content);\n\n\t\t$annotations = $this->getModelAnnotations($models, $this->content);\n\n\t\treturn $this->annotateContent($path, $this->content, $annotations);\n\t}\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return array<string>\n\t */\n\tprotected function getUsedModels(string $content): array {\n\t\tpreg_match_all('/\\$this-\\>fetchModel\\(\\'([a-z.]+)\\'/i', $content, $matches);\n\t\tif (empty($matches[1])) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$models = $matches[1];\n\n\t\treturn array_unique($models);\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t *\n\t * @return string|null\n\t */\n\tprotected function getClassName(string $path, string $content): ?string {\n\t\tpreg_match('#^namespace (.+)\\b#m', $content, $matches);\n\t\tif (!$matches) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$className = pathinfo($path, PATHINFO_FILENAME);\n\n\t\treturn $matches[1] . '\\\\' . $className;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTask/PathAwareClassAnnotatorTaskInterface.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\ClassAnnotatorTask;\n\n/**\n * Optional interface that a class annotator task can implement to declare\n * extra directories that the `bin/cake annotate classes` command should walk\n * in addition to its default `src/` (app + plugin classpaths) and\n * `tests/TestCase/` scans.\n *\n * The intended use is third-party packages whose subjects live outside the\n * conventional source tree — e.g. a test-fixture factory plugin whose\n * subclasses live under `tests/Factory/`. By declaring the path on the\n * task itself, the package does not need to ship its own bake/annotate\n * subcommand: registering the task in `IdeHelper.classAnnotatorTasks` is\n * enough for the existing command to reach the relevant files.\n *\n * `scanPaths()` is `static` so the command can query a task's paths\n * without first instantiating it with an `Io` and per-file content. Paths\n * are project-root relative for app context, and plugin-root relative\n * when the command is run with `-p <plugin>` (or `-p all`). Paths are\n * walked recursively.\n *\n * Convention: return paths with forward slashes and a trailing slash\n * (e.g. `'tests/Factory/'`), independent of OS. The command normalizes\n * to the OS-native separator before walking, so the dedup key is stable\n * when two tasks declare the same path.\n */\ninterface PathAwareClassAnnotatorTaskInterface extends ClassAnnotatorTaskInterface {\n\n\t/**\n\t * @return array<string>\n\t */\n\tpublic static function scanPaths(): array;\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTask/TableFindAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\VariableAnnotation;\nuse PhpParser\\NodeTraverser;\nuse PhpParser\\ParserFactory;\nuse Throwable;\n\n/**\n * Usage of first() or firstOrFail() on table finders should have inline @var annotations added.\n *\n * Detects patterns like:\n * - $entity = $this->TableName->find()->first();\n * - $entity = $this->TableName->find()->firstOrFail();\n */\nclass TableFindAnnotatorTask extends AbstractClassAnnotatorTask implements ClassAnnotatorTaskInterface {\n\n\t/**\n\t * Deprecated: $content, use $this->content instead.\n\t *\n\t * @param string $path\n\t * @param string $content\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path, string $content): bool {\n\t\tif (!str_contains($path, DS . 'src' . DS)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!preg_match('#->(first|firstOrFail)\\(\\)#', $content)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$findings = $this->findTableFinderCalls();\n\t\tif (!$findings) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Process from bottom to top to avoid line number shifts\n\t\tusort($findings, fn ($a, $b) => $b['line'] <=> $a['line']);\n\n\t\t$annotated = false;\n\t\tforeach ($findings as $finding) {\n\t\t\t$annotation = $this->buildVarAnnotation($finding['entityClass'], $finding['varName'], $finding['nullable']);\n\t\t\t$result = $this->annotateInlineContent($path, $this->content, [$annotation], $finding['line']);\n\t\t\tif ($result) {\n\t\t\t\t$annotated = true;\n\t\t\t}\n\t\t}\n\n\t\treturn $annotated;\n\t}\n\n\t/**\n\t * Find all table finder calls that end with first() or firstOrFail().\n\t *\n\t * @return array<array{line: int, varName: string, tableName: string, entityClass: string, nullable: bool}>\n\t */\n\tprotected function findTableFinderCalls(): array {\n\t\t$parser = (new ParserFactory())->createForHostVersion();\n\t\ttry {\n\t\t\t$ast = $parser->parse($this->content);\n\t\t} catch (Throwable $e) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif ($ast === null) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$appNamespace = Configure::read('App.namespace') ?: 'App';\n\n\t\t$visitor = new TableFindNodeVisitor($appNamespace);\n\t\t$traverser = new NodeTraverser();\n\t\t$traverser->addVisitor($visitor);\n\t\t$traverser->traverse($ast);\n\n\t\treturn $visitor->getFindings();\n\t}\n\n\t/**\n\t * Build a @var annotation for the entity type.\n\t *\n\t * @param string $entityClass\n\t * @param string $varName\n\t * @param bool $nullable\n\t * @return \\IdeHelper\\Annotation\\VariableAnnotation\n\t */\n\tprotected function buildVarAnnotation(string $entityClass, string $varName, bool $nullable): VariableAnnotation {\n\t\t$type = $entityClass . ($nullable ? '|null' : '');\n\n\t\t/** @var \\IdeHelper\\Annotation\\VariableAnnotation */\n\t\treturn AnnotationFactory::createOrFail(VariableAnnotation::TAG, $type, '$' . $varName);\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTask/TableFindNodeVisitor.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\Utility\\Inflector;\nuse PhpParser\\Node;\nuse PhpParser\\Node\\Expr\\Assign;\nuse PhpParser\\NodeVisitorAbstract;\n\n/**\n * Node visitor that finds table finder calls ending with first() or firstOrFail().\n */\nclass TableFindNodeVisitor extends NodeVisitorAbstract {\n\n\tprivate string $appNamespace;\n\n\t/**\n\t * @var array<array{line: int, varName: string, tableName: string, entityClass: string, nullable: bool}>\n\t */\n\tprivate array $findings = [];\n\n\t/**\n\t * @param string $appNamespace\n\t */\n\tpublic function __construct(string $appNamespace) {\n\t\t$this->appNamespace = $appNamespace;\n\t}\n\n\t/**\n\t * @return array<array{line: int, varName: string, tableName: string, entityClass: string, nullable: bool}>\n\t */\n\tpublic function getFindings(): array {\n\t\treturn $this->findings;\n\t}\n\n\t/**\n\t * @param \\PhpParser\\Node $node\n\t * @return int|null\n\t */\n\tpublic function enterNode(Node $node): ?int {\n\t\tif (!$node instanceof Assign || !$node->expr instanceof Node\\Expr\\MethodCall) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$methodCall = $node->expr;\n\n\t\t// Check for ->first() or ->firstOrFail()\n\t\tif (\n\t\t\t!$methodCall->name instanceof Node\\Identifier ||\n\t\t\t!in_array($methodCall->name->toString(), ['first', 'firstOrFail'], true)\n\t\t) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$method = $methodCall->name->toString();\n\t\t$nullable = $method === 'first';\n\n\t\t// Get the variable name being assigned\n\t\tif (!$node->var instanceof Node\\Expr\\Variable || !is_string($node->var->name)) {\n\t\t\treturn null;\n\t\t}\n\t\t$varName = $node->var->name;\n\n\t\t// Traverse back through the method chain to find $this->TableName\n\t\t$tableName = $this->extractTableName($methodCall->var);\n\t\tif ($tableName === null) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$entityName = Inflector::singularize($tableName);\n\t\t$entityClass = '\\\\' . $this->appNamespace . '\\\\Model\\\\Entity\\\\' . $entityName;\n\n\t\t$this->findings[] = [\n\t\t\t'line' => $node->getStartLine(),\n\t\t\t'varName' => $varName,\n\t\t\t'tableName' => $tableName,\n\t\t\t'entityClass' => $entityClass,\n\t\t\t'nullable' => $nullable,\n\t\t];\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Extract table name from method chain like $this->TableName->find()->...\n\t *\n\t * @param \\PhpParser\\Node\\Expr $expr\n\t * @return string|null\n\t */\n\tprivate function extractTableName(Node\\Expr $expr): ?string {\n\t\t// Walk back through the method chain\n\t\twhile ($expr instanceof Node\\Expr\\MethodCall) {\n\t\t\t$expr = $expr->var;\n\t\t}\n\n\t\t// Should now be at $this->TableName\n\t\tif (\n\t\t\t$expr instanceof Node\\Expr\\PropertyFetch &&\n\t\t\t$expr->var instanceof Node\\Expr\\Variable &&\n\t\t\t$expr->var->name === 'this' &&\n\t\t\t$expr->name instanceof Node\\Identifier\n\t\t) {\n\t\t\treturn $expr->name->toString();\n\t\t}\n\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTask/TestClassAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\LinkAnnotation;\nuse IdeHelper\\Annotation\\UsesAnnotation;\n\n/**\n * Classes that test a class in a magic-call way should automatically have `@link` annotated.\n * By default:\n * - Controller tests\n * - Command tests\n *\n * Use Configure key `IdeHelper.testClassPatterns` to add more types and their regex pattern.\n */\nclass TestClassAnnotatorTask extends AbstractClassAnnotatorTask implements PathAwareClassAnnotatorTaskInterface {\n\n\t/**\n\t * @return array<string>\n\t */\n\tpublic static function scanPaths(): array {\n\t\treturn ['tests/TestCase/'];\n\t}\n\n\t/**\n\t * Deprecated: $content, use $this->content instead.\n\t *\n\t * @param string $path\n\t * @param string $content\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path, string $content): bool {\n\t\tif (!str_contains($path, DS . 'tests' . DS . 'TestCase' . DS)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$defaultTypes = (array)Configure::read('IdeHelper.testClassPatterns');\n\t\t$types = $defaultTypes + [\n\t\t\t'Controller' => '#\\bclass .+ControllerTest extends\\b#',\n\t\t\t'Command' => '#\\bclass .+CommandTest extends\\b#',\n\t\t];\n\t\t$typeList = implode('|', array_keys($types));\n\n\t\tif (!preg_match('#^namespace .+\\\\\\\\Test\\\\\\\\TestCase\\\\\\\\(' . $typeList . ')\\b#m', $content)) {\n\t\t\treturn false;\n\t\t}\n\t\tif (!$this->matchesType($content, $types)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param array<string> $types\n\t * @return bool\n\t */\n\tprotected function matchesType(string $content, array $types): bool {\n\t\tforeach ($types as $type => $pattern) {\n\t\t\tif (preg_match($pattern, $content)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$class = $this->getTestedClass($this->content);\n\t\tif (!$class) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($this->hasUsesClassAttribute($this->content, $class)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($this->hasLinkAnnotation($this->content, $class)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$annotations = $this->buildLinkAnnotations([$class]);\n\n\t\treturn $this->annotateContent($path, $this->content, $annotations);\n\t}\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return string|null\n\t */\n\tprotected function getTestedClass(string $content): ?string {\n\t\tpreg_match('#namespace (.+);#', $content, $matches);\n\t\tif (!$matches) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$namespace = str_replace('\\\\Test\\\\TestCase\\\\', '\\\\', $matches[1]);\n\n\t\tpreg_match('#\\bclass (.+)Test extends#', $content, $matches);\n\t\tif (!$matches) {\n\t\t\treturn null;\n\t\t}\n\t\t$className = $matches[1];\n\n\t\t$fullClassName = $namespace . '\\\\' . $className;\n\t\tif (!class_exists($fullClassName)) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn $fullClassName;\n\t}\n\n\t/**\n\t * @param array<string> $classes\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function buildLinkAnnotations(array $classes): array {\n\t\t$annotations = [];\n\n\t\t$tag = UsesAnnotation::TAG;\n\t\tif (Configure::read('IdeHelper.preferLinkOverUsesInTests') ?? true) {\n\t\t\t$tag = LinkAnnotation::TAG;\n\t\t}\n\n\t\tforeach ($classes as $className) {\n\t\t\t$annotations[] = AnnotationFactory::createOrFail($tag, '\\\\' . $className);\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param string $class\n\t * @return bool\n\t */\n\tprotected function hasUsesClassAttribute(string $content, string $class): bool {\n\t\t$shortClassName = substr(strrchr($class, '\\\\') ?: $class, 1);\n\t\t$full = preg_quote($class, '#');\n\t\t$short = preg_quote($shortClassName, '#');\n\t\t$pattern = '#\\#\\[UsesClass\\(\\s*\\\\\\\\?(?:' . $full . '|' . $short . ')::class\\s*\\)\\]#';\n\n\t\treturn (bool)preg_match($pattern, $content);\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param string $class\n\t * @return bool\n\t */\n\tprotected function hasLinkAnnotation(string $content, string $class): bool {\n\t\t$shortClassName = substr(strrchr($class, '\\\\') ?: $class, 1);\n\t\t$full = preg_quote($class, '#');\n\t\t$short = preg_quote($shortClassName, '#');\n\t\t$pattern = '#@(uses|link)\\s+\\\\\\\\?(?:' . $full . '|' . $short . ')\\b#';\n\n\t\treturn (bool)preg_match($pattern, $content);\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ClassAnnotatorTaskCollection.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\FormClassAnnotatorTask;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\MailerClassAnnotatorTask;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\ModelAwareClassAnnotatorTask;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\TestClassAnnotatorTask;\nuse IdeHelper\\Console\\Io;\n\nclass ClassAnnotatorTaskCollection {\n\n\t/**\n\t * @var array<class-string<\\IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface>, class-string<\\IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface>>\n\t */\n\tprotected array $defaultTasks = [\n\t\tModelAwareClassAnnotatorTask::class => ModelAwareClassAnnotatorTask::class,\n\t\tFormClassAnnotatorTask::class => FormClassAnnotatorTask::class,\n\t\tMailerClassAnnotatorTask::class => MailerClassAnnotatorTask::class,\n\t\tTestClassAnnotatorTask::class => TestClassAnnotatorTask::class,\n\t];\n\n\t/**\n\t * @var array<class-string<\\IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface>>\n\t */\n\tprotected array $tasks;\n\n\t/**\n\t * @param array<class-string<\\IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface>> $tasks\n\t */\n\tpublic function __construct(array $tasks = []) {\n\t\t$defaultTasks = $this->defaultTasks();\n\t\t$tasks += $defaultTasks;\n\t\t$this->tasks = $tasks;\n\t}\n\n\t/**\n\t * @return array<class-string<\\IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface>>\n\t */\n\tpublic function defaultTasks(): array {\n\t\t$tasks = (array)Configure::read('IdeHelper.classAnnotatorTasks') + $this->defaultTasks;\n\n\t\tforeach ($tasks as $k => $v) {\n\t\t\tif (is_numeric($k)) {\n\t\t\t\t$tasks[$v] = $v;\n\t\t\t\tunset($tasks[$k]);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!$v) {\n\t\t\t\tunset($tasks[$k]);\n\t\t\t}\n\t\t}\n\n\t\treturn $tasks;\n\t}\n\n\t/**\n\t * @param \\IdeHelper\\Console\\Io $io\n\t * @param array<string, mixed> $config\n\t * @param string $content\n\t * @return array<\\IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface>\n\t */\n\tpublic function tasks(Io $io, array $config, string $content): array {\n\t\t$tasks = $this->tasks;\n\n\t\t$collection = [];\n\t\tforeach ($tasks as $task) {\n\t\t\t/** @var \\IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface $object */\n\t\t\t$object = new $task($io, $config, $content);\n\t\t\t$collection[] = $object;\n\t\t}\n\n\t\treturn $collection;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/CommandAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse IdeHelper\\Annotator\\Traits\\ModelTrait;\nuse RuntimeException;\n\nclass CommandAnnotator extends AbstractAnnotator {\n\n\tuse ModelTrait;\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$className = pathinfo($path, PATHINFO_FILENAME);\n\t\tif ($className === 'Command' || !str_ends_with($className, 'Command')) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\t\t$primaryModelClass = $this->getPrimaryModelClass($content);\n\t\t$usedModels = $this->getUsedModels($content);\n\t\tif ($primaryModelClass) {\n\t\t\t$usedModels[] = $primaryModelClass;\n\t\t}\n\t\t$usedModels = array_unique($usedModels);\n\n\t\t$annotations = $this->getModelAnnotations($usedModels, $content);\n\n\t\treturn $this->annotateContent($path, $content, $annotations);\n\t}\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return string|null\n\t */\n\tprotected function getPrimaryModelClass(string $content): ?string {\n\t\tif (!preg_match('/\\bprotected \\?string \\$defaultTable = \\'([a-z.\\/]+)\\'/i', $content, $matches)) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$modelName = $matches[1];\n\n\t\treturn $modelName;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ComponentAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Cake\\Controller\\ComponentRegistry;\nuse Cake\\Controller\\Controller;\nuse Cake\\Core\\Configure;\nuse Cake\\Http\\ServerRequest;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\MethodAnnotation;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse IdeHelper\\Annotator\\Traits\\ComponentTrait;\nuse IdeHelper\\Utility\\App;\nuse RuntimeException;\nuse Throwable;\n\nclass ComponentAnnotator extends AbstractAnnotator {\n\n\tuse ComponentTrait;\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\tif (!str_ends_with($name, 'Component')) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$name = substr($name, 0, -9);\n\t\t$plugin = $this->getConfig(static::CONFIG_PLUGIN);\n\t\t/** @phpstan-var class-string<object>|null $className */\n\t\t$className = App::className(($plugin ? $plugin . '.' : '') . $name, 'Controller/Component', 'Component');\n\t\tif (!$className) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\t\t$annotations = $this->buildAnnotations($className);\n\n\t\tif ($this->hasControllerAnnotation($content)) {\n\t\t\t$appControllerClass = (Configure::read('App.namespace') ?: 'App') . '\\Controller\\AppController';\n\t\t\t$annotations[] = new MethodAnnotation('\\\\' . $appControllerClass, 'getController()');\n\t\t}\n\n\t\treturn $this->annotateContent($path, $content, $annotations);\n\t}\n\n\t/**\n\t * @param class-string<object> $className\n\t *\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function buildAnnotations(string $className): array {\n\t\t$annotations = [];\n\n\t\t$componentAnnotations = $this->getComponentAnnotations($className);\n\t\tforeach ($componentAnnotations as $componentAnnotation) {\n\t\t\t$annotations[] = $componentAnnotation;\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * @param class-string<object> $className $className\n\t *\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function getComponentAnnotations(string $className) {\n\t\tif ($this->_isAbstract($className)) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$controller = new Controller(new ServerRequest());\n\t\ttry {\n\t\t\t$object = new $className(new ComponentRegistry($controller));\n\t\t} catch (Throwable $e) {\n\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t$this->_io->warn('   Skipping component annotations: ' . $e->getMessage());\n\t\t\t}\n\n\t\t\treturn [];\n\t\t}\n\n\t\t$map = $this->invokeProperty($object, 'components');\n\n\t\tif (!$map) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$annotations = [];\n\t\tforeach ($map as $name => $config) {\n\t\t\t$className = $this->findClassName($config['className'] ?? $name, !$this->getConfig(static::CONFIG_PLUGIN));\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$annotations[] = AnnotationFactory::createOrFail(PropertyAnnotation::TAG, '\\\\' . $className, '$' . $name);\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return bool\n\t */\n\tprotected function hasControllerAnnotation(string $content): bool {\n\t\treturn str_contains($content, '$this->getController()');\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ControllerAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Datasource\\ResultSetInterface;\nuse Cake\\Http\\ServerRequest;\nuse Cake\\ORM\\Entity;\nuse Cake\\ORM\\TableRegistry;\nuse Cake\\Utility\\Inflector;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\MethodAnnotation;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse IdeHelper\\Annotator\\Traits\\ComponentTrait;\nuse IdeHelper\\Annotator\\Traits\\ModelTrait;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\GenericString;\nuse PHP_CodeSniffer\\Files\\File;\nuse RuntimeException;\nuse Throwable;\n\nclass ControllerAnnotator extends AbstractAnnotator {\n\n\tuse ComponentTrait;\n\tuse ModelTrait;\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$className = pathinfo($path, PATHINFO_FILENAME);\n\t\tif (!str_ends_with($className, 'Controller')) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\t\t$primaryModelName = $this->getPrimaryModelClass($content, $className, $path);\n\n\t\t$usedModels = $this->getUsedModels($content);\n\t\tif ($primaryModelName) {\n\t\t\t$usedModels[] = $primaryModelName;\n\t\t}\n\t\t$usedModels = array_unique($usedModels);\n\n\t\t$annotations = $this->getModelAnnotations($usedModels, $content);\n\n\t\t$componentAnnotations = $this->getComponentAnnotations($className, $path);\n\t\tforeach ($componentAnnotations as $componentAnnotation) {\n\t\t\t$annotations[] = $componentAnnotation;\n\t\t}\n\n\t\t$paginationAnnotations = $this->getPaginationAnnotations($content, $primaryModelName);\n\t\tforeach ($paginationAnnotations as $paginationAnnotation) {\n\t\t\t$annotations[] = $paginationAnnotation;\n\t\t}\n\n\t\t$annotations = $this->filterByTypedProperties($annotations, $content);\n\n\t\treturn $this->annotateContent($path, $content, $annotations);\n\t}\n\n\t/**\n\t * Set annotations to be removable. Fixes false positives with PHPStan thinking it's all good.\n\t *\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $closeTagIndex\n\t * @param array<string> $types\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function parseExistingAnnotations(File $file, int $closeTagIndex, array $types = self::TYPES): array {\n\t\t$annotations = parent::parseExistingAnnotations($file, $closeTagIndex, $types);\n\n\t\tforeach ($annotations as &$annotation) {\n\t\t\tif (!preg_match('#\\\\\\\\Model\\\\\\\\Table\\\\\\\\#', $annotation->getType()) && !preg_match('#\\\\\\\\Controller\\\\\\\\Component\\\\\\\\#', $annotation->getType())) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$annotation->setInUse(false);\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param string $className\n\t * @param string $path\n\t * @return string|null\n\t */\n\tprotected function getPrimaryModelClass(string $content, string $className, string $path): ?string {\n\t\tif ($className === 'AppController' || preg_match('#[a-z0-9]AppController$#', $className)) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$dynamicallyFoundModelClass = $this->findModelClass($className, $path);\n\t\tif ($dynamicallyFoundModelClass !== null) {\n\t\t\treturn $dynamicallyFoundModelClass !== '' ? $dynamicallyFoundModelClass : null;\n\t\t}\n\n\t\tif (preg_match('/\\bprotected \\?string \\$defaultTable = \\'([a-z.\\/]+)\\'/i', $content, $matches)) {\n\t\t\treturn $matches[1];\n\t\t}\n\n\t\tif (preg_match('/\\bprotected \\?string \\$defaultTable = \\'\\';/i', $content, $matches)) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$plugin = $this->getConfig(static::CONFIG_PLUGIN);\n\t\t$modelName = substr($className, 0, -10);\n\t\t$modelClassName = ($plugin ?: Configure::read('App.namespace', 'App')) . '\\\\Model\\\\Table\\\\' . $modelName . 'Table';\n\t\tif (!class_exists($modelClassName)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif ($modelName && $plugin) {\n\t\t\t$modelName = $plugin . '.' . $modelName;\n\t\t}\n\n\t\treturn $modelName ?: null;\n\t}\n\n\t/**\n\t * @param string $className\n\t * @param string $path\n\t *\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function getComponentAnnotations(string $className, string $path): array {\n\t\ttry {\n\t\t\t$map = $this->getUsedComponents($className, $path);\n\t\t} catch (Throwable $e) {\n\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t$this->_io->warn('   Skipping component annotations: ' . $e->getMessage());\n\t\t\t}\n\t\t}\n\n\t\tif (empty($map)) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$annotations = [];\n\t\tforeach ($map as $component => $className) {\n\t\t\tif (str_starts_with($className, 'Cake\\\\')) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$annotations[] = AnnotationFactory::createOrFail(PropertyAnnotation::TAG, '\\\\' . $className, '$' . $component);\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * @param string $className\n\t * @param string $path\n\t *\n\t * @return array<string>\n\t */\n\tprotected function getUsedComponents(string $className, string $path): array {\n\t\t$plugin = $className !== 'AppController' ? $this->getConfig(static::CONFIG_PLUGIN) : null;\n\t\t$prefix = $this->getPrefix($className, $path);\n\n\t\t/** @phpstan-var class-string<object>|null $fullClassName */\n\t\t$fullClassName = App::className(($plugin ? $plugin . '.' : '') . $className, 'Controller' . $prefix);\n\t\tif (!$fullClassName) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif ($this->_isAbstract($fullClassName)) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$request = new ServerRequest(['url' => 'justfortesting']);\n\t\t/** @var \\App\\Controller\\AppController $controller */\n\t\t$controller = new $fullClassName($request);\n\n\t\t$components = [];\n\t\tforeach ($controller->components()->loaded() as $component) {\n\t\t\t$components[$component] = get_class($controller->components()->get($component));\n\t\t}\n\n\t\tif ($className === 'AppController') {\n\t\t\treturn $components;\n\t\t}\n\n\t\t$appControllerComponents = $this->getUsedComponents('AppController', $path);\n\t\t$components = array_diff_key($components, $appControllerComponents);\n\n\t\treturn $components;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param string|null $primaryModelName\n\t *\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function getPaginationAnnotations(string $content, ?string $primaryModelName): array {\n\t\t$entities = $this->extractPaginateEntities($content, $primaryModelName);\n\t\tif (!$entities) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$resultSetInterfaceCollection = GenericString::generate(implode('|', $entities), '\\\\' . ResultSetInterface::class);\n\n\t\t$settingsType = 'array';\n\t\tif (Configure::read('IdeHelper.genericsInParam')) {\n\t\t\t$settingsType = 'array<string, mixed>';\n\t\t}\n\n\t\t$annotations = [AnnotationFactory::createOrFail(MethodAnnotation::TAG, $resultSetInterfaceCollection, 'paginate(\\Cake\\Datasource\\RepositoryInterface|\\Cake\\Datasource\\QueryInterface|string|null $object = null, ' . $settingsType . ' $settings = [])')];\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param string|null $primaryModelName\n\t *\n\t * @return array<string>\n\t */\n\tprotected function extractPaginateEntities(string $content, ?string $primaryModelName): array {\n\t\t$models = [];\n\n\t\tpreg_match_all('/\\$this->paginate\\(\\)/i', $content, $matches);\n\t\tif (!empty($matches[0]) && $primaryModelName) {\n\t\t\t$models[] = $primaryModelName;\n\t\t}\n\n\t\tpreg_match_all('/\\$this->paginate\\(\\$this->([a-z]+)\\)/i', $content, $matches);\n\t\tif (!empty($matches[1])) {\n\t\t\t$models = array_merge($models, $matches[1]);\n\t\t}\n\n\t\tif (!$models) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$result = [];\n\t\tforeach ($models as $model) {\n\t\t\t$entityClassName = $this->getEntity($model, $primaryModelName);\n\n\t\t\t$fullClassName = '\\\\' . ltrim($entityClassName, '\\\\');\n\t\t\tif (in_array($fullClassName, $result, true)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$result[] = $fullClassName;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param string $modelName\n\t * @param string|null $primaryModelName Can be plugin dot syntaxed\n\t *\n\t * @return string\n\t */\n\tprotected function getEntity(string $modelName, ?string $primaryModelName): string {\n\t\tif ($this->getConfig(static::CONFIG_PLUGIN) && $modelName !== $primaryModelName && !strpos($modelName, '.')) {\n\t\t\t$modelName = $this->getConfig(static::CONFIG_PLUGIN) . '.' . $modelName;\n\t\t}\n\n\t\ttry {\n\t\t\t$table = TableRegistry::getTableLocator()->get($modelName);\n\t\t\t$entityClassName = $table->getEntityClass();\n\t\t} catch (Throwable $exception) {\n\t\t\t$plugin = null;\n\t\t\tif (str_contains($modelName, '.')) {\n\t\t\t\t[$plugin, $modelName] = explode('.', $modelName, 2);\n\t\t\t}\n\t\t\t$entity = Inflector::singularize($modelName);\n\t\t\t$fullClassName = ($plugin ?: Configure::read('App.namespace', 'App')) . '\\\\Model\\\\Entity\\\\' . $entity;\n\t\t\tif (class_exists($fullClassName)) {\n\t\t\t\treturn $fullClassName;\n\t\t\t}\n\n\t\t\treturn Entity::class;\n\t\t}\n\n\t\treturn $entityClassName;\n\t}\n\n\t/**\n\t * @param string $className\n\t * @param string $path\n\t *\n\t * @return string|null\n\t */\n\tprotected function findModelClass(string $className, string $path): ?string {\n\t\t$plugin = $this->getConfig(static::CONFIG_PLUGIN);\n\t\t$prefix = $this->getPrefix($className, $path);\n\n\t\t$fullClassName = App::className(($plugin ? $plugin . '.' : '') . $className, 'Controller' . $prefix);\n\t\tif (!$fullClassName) {\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\t/** @var \\Cake\\Controller\\Controller $controller */\n\t\t\t$controller = new $fullClassName(new ServerRequest());\n\t\t} catch (Throwable $e) {\n\t\t\t$this->_io->warn('   Could not look up model class for ' . $fullClassName . ': ' . $e->getMessage());\n\n\t\t\treturn null;\n\t\t}\n\n\t\t$modelClass = $this->invokeProperty($controller, 'defaultTable');\n\t\tif (!$modelClass) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif ($this->getConfig(static::CONFIG_PLUGIN) && !str_contains($modelClass, '.')) {\n\t\t\t$modelClass = $this->getConfig(static::CONFIG_PLUGIN) . '.' . $modelClass;\n\t\t}\n\n\t\t$fullClassName = App::className($modelClass, 'Model/Table', 'Table');\n\t\tif (!$fullClassName) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn $modelClass;\n\t}\n\n\t/**\n\t * Namespace prefix for controllers.\n\t *\n\t * @param string $className\n\t * @param string $path\n\t *\n\t * @return string\n\t */\n\tprotected function getPrefix(string $className, string $path): string {\n\t\tpreg_match('#/Controller/(\\w+)/' . $className . '\\.php#', $path, $matches);\n\t\t$prefix = '';\n\t\tif ($matches) {\n\t\t\t$prefix = '/' . $matches[1];\n\t\t}\n\n\t\treturn $prefix;\n\t}\n\n\t/**\n\t * Filter out Table property annotations for properties that already have typed declarations.\n\t *\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $annotations\n\t * @param string $content\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function filterByTypedProperties(array $annotations, string $content): array {\n\t\tpreg_match_all('/(?:protected|public|private)\\s+\\??[\\w\\\\\\\\]+\\s+\\$(\\w+)\\s*[;=]/', $content, $matches);\n\t\t$typedProperties = $matches[1];\n\t\tif (!$typedProperties) {\n\t\t\treturn $annotations;\n\t\t}\n\n\t\treturn array_filter($annotations, function ($annotation) use ($typedProperties) {\n\t\t\tif (!$annotation instanceof PropertyAnnotation) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (!preg_match('#\\\\\\\\Model\\\\\\\\Table\\\\\\\\#', $annotation->getType())) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t$property = ltrim($annotation->getProperty(), '$');\n\n\t\t\treturn !in_array($property, $typedProperties, true);\n\t\t});\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/EntityAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Cake\\Core\\Configure;\nuse Cake\\ORM\\Association;\nuse Cake\\ORM\\Association\\BelongsToMany;\nuse Cake\\ORM\\Entity;\nuse Cake\\ORM\\Table;\nuse Cake\\ORM\\TableRegistry;\nuse Cake\\Utility\\Inflector;\nuse Cake\\View\\View;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse IdeHelper\\Annotation\\PropertyReadAnnotation;\nuse IdeHelper\\Annotator\\Traits\\UseStatementsTrait;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\View\\Helper\\DocBlockHelper;\nuse PHP_CodeSniffer\\Files\\File;\nuse RuntimeException;\nuse Throwable;\n\nclass EntityAnnotator extends AbstractAnnotator {\n\n\tuse UseStatementsTrait;\n\n\t/**\n\t * @var array<string, string>|null\n\t */\n\tprotected static $typeMap;\n\n\t/**\n\t * @var array<string, string>\n\t */\n\tprotected static array $typeMapDefaults = [\n\t\t'mediumtext' => 'string',\n\t\t'longtext' => 'string',\n\t\t'array' => 'array',\n\t\t'json' => 'array',\n\t\t'binaryuuid' => 'string',\n\t];\n\n\t/**\n\t * @param string $path Path to file.\n\t * @throws \\RuntimeException\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\tif ($name === 'Entity') {\n\t\t\treturn false;\n\t\t}\n\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\n\t\t$helper = new DocBlockHelper(new View());\n\t\t$propertyHintMap = $this->propertyHintMap($content, $helper);\n\n\t\t$virtualFields = $this->virtualFields($name);\n\t\t// For BC reasons we cannot pass it as 3rd param, so we transport it on the helper as setter/getter\n\t\t$helper->setVirtualFields($virtualFields);\n\t\t$annotations = $this->buildAnnotations($propertyHintMap, $helper);\n\n\t\treturn $this->annotateContent($path, $content, $annotations);\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param \\IdeHelper\\View\\Helper\\DocBlockHelper $helper\n\t * @return array<string>\n\t */\n\tprotected function propertyHintMap(string $content, DocBlockHelper $helper): array {\n\t\t/** @var \\Cake\\Database\\Schema\\TableSchemaInterface $tableSchema */\n\t\t$tableSchema = $this->getConfig('schema');\n\t\t$columns = $tableSchema->columns();\n\n\t\t$schema = [];\n\t\tforeach ($columns as $column) {\n\t\t\t$row = $tableSchema->getColumn($column);\n\t\t\t$row['kind'] = 'column';\n\t\t\t$schema[$column] = $row;\n\t\t}\n\n\t\t$schema = $this->hydrateSchemaFromAssoc($schema);\n\n\t\t$propertyHintMap = $helper->buildEntityPropertyHintTypeMap($schema);\n\t\t$propertyHintMap = $this->buildExtendedEntityPropertyHintTypeMap($schema, $helper) + $propertyHintMap;\n\t\t$propertyHintMap += $this->buildVirtualPropertyHintTypeMap($content);\n\t\t$propertyHintMap += $helper->buildEntityAssociationHintTypeMap($schema);\n\n\t\treturn array_filter($propertyHintMap);\n\t}\n\n\t/**\n\t * From Bake Plugin\n\t *\n\t * @param array<string, mixed> $schema\n\t *\n\t * @return array<string, array<string, mixed>>\n\t */\n\tprotected function hydrateSchemaFromAssoc(array $schema): array {\n\t\t/** @var \\Cake\\ORM\\AssociationCollection<\\Cake\\ORM\\Association> $associations */\n\t\t$associations = $this->getConfig('associations');\n\n\t\t/** @var \\Cake\\ORM\\Association $association */\n\t\tforeach ($associations as $association) {\n\t\t\ttry {\n\t\t\t\t$entityClass = '\\\\' . ltrim($association->getTarget()->getEntityClass(), '\\\\');\n\n\t\t\t\tif ($entityClass === '\\\\' . Entity::class) {\n\t\t\t\t\t$namespace = Configure::read('App.namespace');\n\n\t\t\t\t\t[$plugin] = pluginSplit($association->getTarget()->getRegistryAlias());\n\t\t\t\t\tif ($plugin !== null) {\n\t\t\t\t\t\t$namespace = $plugin;\n\t\t\t\t\t}\n\t\t\t\t\t$namespace = str_replace('/', '\\\\', trim($namespace, '\\\\'));\n\n\t\t\t\t\t$entityClass = $this->entityName($association->getTarget()->getAlias());\n\t\t\t\t\t$entityClass = '\\\\' . $namespace . '\\Model\\Entity\\\\' . $entityClass;\n\n\t\t\t\t\tif (!class_exists($entityClass)) {\n\t\t\t\t\t\t$entityClass = '\\\\' . Entity::class;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t$schema[$association->getProperty()] = [\n\t\t\t\t\t'kind' => 'association',\n\t\t\t\t\t'association' => $association,\n\t\t\t\t\t'type' => $entityClass,\n\t\t\t\t\t'null' => $this->nullable($association, $schema),\n\t\t\t\t];\n\n\t\t\t\tif ($association->type() !== 'manyToMany') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t/** @var \\Cake\\ORM\\Association\\BelongsToMany<\\Cake\\ORM\\Table> $association */\n\t\t\t\t$table = $this->getThrough($association);\n\t\t\t\tif (!$table) {\n\t\t\t\t\t$table = new Table();\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\t$className = $table->getEntityClass();\n\t\t\t\t} catch (Throwable $e) {\n\t\t\t\t\t$className = Entity::class;\n\t\t\t\t}\n\n\t\t\t\t$entityClass = '\\\\' . ltrim($className, '\\\\');\n\t\t\t\t$alias = $association->getTarget()->getAlias() . 'Join';\n\t\t\t\t$table->addAssociations(['belongsTo' => [$alias]]);\n\t\t\t\t$schema['_joinData'] = [\n\t\t\t\t\t'kind' => 'association',\n\t\t\t\t\t'association' => $table->{$alias},\n\t\t\t\t\t'type' => $entityClass,\n\t\t\t\t\t'null' => false,\n\t\t\t\t];\n\n\t\t\t} catch (Throwable $exception) {\n\t\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t\t$this->_io->warn($exception->getMessage());\n\t\t\t\t}\n\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\treturn $schema;\n\t}\n\n\t/**\n\t * @param \\Cake\\ORM\\Association\\BelongsToMany<\\Cake\\ORM\\Table> $association\n\t *\n\t * @return \\Cake\\ORM\\Table|null\n\t */\n\tprotected function getThrough(BelongsToMany $association): ?Table {\n\t\ttry {\n\t\t\t$through = $association->getThrough();\n\t\t} catch (Throwable) {\n\t\t\t$through = null;\n\t\t}\n\t\tif ($through) {\n\t\t\tif (is_object($through)) {\n\t\t\t\treturn $through;\n\t\t\t}\n\n\t\t\treturn TableRegistry::getTableLocator()->get($through);\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @uses \\Cake\\ORM\\Association\\BelongsToMany::_junctionTableName()\n\t *\n\t * @param \\Cake\\ORM\\Association\\BelongsToMany<\\Cake\\ORM\\Table> $association\n\t * @return string\n\t */\n\tprotected function junctionTableName(BelongsToMany $association): string {\n\t\t$tablesNames = array_map('Cake\\Utility\\Inflector::underscore', [\n\t\t\t$association->getSource()->getTable(),\n\t\t\t$association->getTarget()->getTable(),\n\t\t]);\n\n\t\tsort($tablesNames);\n\n\t\treturn implode('_', $tablesNames);\n\t}\n\n\t/**\n\t * @param \\Cake\\ORM\\Association $association\n\t * @param array<string, mixed> $schema\n\t * @return bool\n\t */\n\tprotected function nullable(Association $association, array $schema): bool {\n\t\tif ($association->type() === Association::ONE_TO_ONE) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif ($association->type() === Association::MANY_TO_ONE) {\n\t\t\t/** @var array<string>|string $field */\n\t\t\t$field = $association->getForeignKey();\n\t\t\tif (is_array($field)) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (!isset($schema[$field]['null'])) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn $schema[$field]['null'];\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Creates the proper entity name (singular) for the specified name\n\t *\n\t * @param string $name Name\n\t * @return string Camelized and plural model name\n\t */\n\tprotected function entityName(string $name): string {\n\t\treturn Inflector::singularize(Inflector::camelize($name));\n\t}\n\n\t/**\n\t * @param array<string, array<string, mixed>> $propertySchema\n\t * @param \\IdeHelper\\View\\Helper\\DocBlockHelper $helper\n\t *\n\t * @return array<string, string>\n\t */\n\tprotected function buildExtendedEntityPropertyHintTypeMap(array $propertySchema, DocBlockHelper $helper): array {\n\t\t$propertyHintMap = [];\n\n\t\tforeach ($propertySchema as $property => $info) {\n\t\t\tif ($info['kind'] === 'column') {\n\t\t\t\t$type = $this->columnTypeToHintType($info['type']);\n\t\t\t\tif ($type === null) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$propertyHintMap[$property] = $helper->columnTypeNullable($info, $type);\n\t\t\t}\n\t\t}\n\n\t\treturn $propertyHintMap;\n\t}\n\n\t/**\n\t * Converts a column type to its DocBlock type counterpart.\n\t *\n\t * @see \\Cake\\Database\\Type\n\t *\n\t * @param string $type The column type.\n\t * @return string|null The DocBlock type, or `null` for unsupported column types.\n\t */\n\tprotected function columnTypeToHintType(string $type): ?string {\n\t\tif (static::$typeMap === null) {\n\t\t\tstatic::$typeMap = (array)Configure::read('IdeHelper.typeMap') + static::$typeMapDefaults;\n\t\t}\n\n\t\tif (isset(static::$typeMap[$type])) {\n\t\t\treturn static::$typeMap[$type];\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @return array<string, string>\n\t */\n\tprotected function buildVirtualPropertyHintTypeMap(string $content): array {\n\t\tif (!preg_match('#\\bfunction _get[A-Z][a-zA-Z0-9]+\\(\\)#', $content)) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$file = $this->getFile('', $content);\n\n\t\t$classIndex = $file->findNext(T_CLASS, 0);\n\t\tif ($classIndex === false) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$tokens = $file->getTokens();\n\t\tif (empty($tokens[$classIndex]['scope_closer'])) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$useStatements = null;\n\n\t\t$classEndIndex = $tokens[$classIndex]['scope_closer'];\n\n\t\t$properties = [];\n\t\t$startIndex = $classIndex;\n\t\twhile ($startIndex < $classEndIndex) {\n\t\t\t$functionIndex = $file->findNext(T_FUNCTION, $startIndex + 1);\n\t\t\tif ($functionIndex === false) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t$methodNameIndex = $file->findNext(T_STRING, $functionIndex + 1);\n\t\t\tif ($methodNameIndex === false) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t$token = $tokens[$methodNameIndex];\n\t\t\t$methodName = $token['content'];\n\n\t\t\t$startIndex = $methodNameIndex + 1;\n\n\t\t\tif (!preg_match('#^_get([A-Z][a-zA-Z0-9]+)$#', $methodName, $matches)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$property = Inflector::underscore($matches[1]);\n\n\t\t\t$type = $this->returnType($file, $tokens, $functionIndex);\n\t\t\tif ($useStatements === null) {\n\t\t\t\t$useStatements = $this->getUseStatements($file);\n\t\t\t}\n\t\t\t$type = $this->fqcnIfNeeded($type, $useStatements);\n\n\t\t\t$properties[$property] = $type;\n\t\t}\n\n\t\treturn $properties;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<array<string, mixed>> $tokens\n\t * @param int $functionIndex\n\t * @return string\n\t */\n\tprotected function returnType(File $file, array $tokens, int $functionIndex): string {\n\t\t$firstTokenInLineIndex = $functionIndex;\n\n\t\t$line = $tokens[$functionIndex]['line'];\n\n\t\twhile ($tokens[$firstTokenInLineIndex - 1]['line'] === $line) {\n\t\t\t$firstTokenInLineIndex--;\n\t\t}\n\n\t\t$docBlockCloseTagIndex = $this->findDocBlockCloseTagIndex($file, $firstTokenInLineIndex);\n\t\tif (!$docBlockCloseTagIndex || empty($tokens[$docBlockCloseTagIndex]['comment_opener'])) {\n\t\t\treturn $this->typeHint($file, $tokens, $functionIndex);\n\t\t}\n\n\t\t$docBlockOpenTagIndex = $tokens[$docBlockCloseTagIndex]['comment_opener'];\n\n\t\treturn $this->extractReturnType($tokens, $docBlockOpenTagIndex, $docBlockCloseTagIndex);\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<array<string, mixed>> $tokens\n\t * @param int $functionIndex\n\t *\n\t * @return string\n\t */\n\tprotected function typeHint(File $file, array $tokens, int $functionIndex): string {\n\t\t$parenthesisCloseTagIndex = $tokens[$functionIndex]['parenthesis_closer'];\n\t\t$scopeOpenTagIndex = $tokens[$functionIndex]['scope_opener'];\n\n\t\t$typehintIndex = $file->findNext(T_STRING, $parenthesisCloseTagIndex + 1, $scopeOpenTagIndex);\n\t\tif ($typehintIndex === false) {\n\t\t\treturn 'mixed';\n\t\t}\n\n\t\t$returnType = $tokens[$typehintIndex]['content'];\n\n\t\t$nullableIndex = $file->findNext(T_NULLABLE, $parenthesisCloseTagIndex + 1, $typehintIndex);\n\n\t\tif ($nullableIndex) {\n\t\t\t$returnType .= '|null';\n\t\t}\n\n\t\treturn $returnType;\n\t}\n\n\t/**\n\t * @param array<array<string, mixed>> $tokens\n\t * @param int $docBlockOpenTagIndex\n\t * @param int $docBlockCloseTagIndex\n\t *\n\t * @return string\n\t */\n\tprotected function extractReturnType(array $tokens, int $docBlockOpenTagIndex, int $docBlockCloseTagIndex): string {\n\t\tfor ($i = $docBlockOpenTagIndex + 1; $i < $docBlockCloseTagIndex; $i++) {\n\n\t\t\tif ($tokens[$i]['type'] !== 'T_DOC_COMMENT_TAG') {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ($tokens[$i]['content'] !== '@return') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$classNameIndex = $i + 2;\n\n\t\t\tif ($tokens[$classNameIndex]['type'] !== 'T_DOC_COMMENT_STRING') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$content = $tokens[$classNameIndex]['content'];\n\n\t\t\t// Find the end of the type, accounting for nested generics\n\t\t\t// Spaces inside <> brackets should not be treated as type boundaries\n\t\t\t$bracketDepth = 0;\n\t\t\t$typeEnd = null;\n\t\t\t$length = strlen($content);\n\t\t\tfor ($j = 0; $j < $length; $j++) {\n\t\t\t\t$char = $content[$j];\n\t\t\t\tif ($char === '<') {\n\t\t\t\t\t$bracketDepth++;\n\t\t\t\t} elseif ($char === '>') {\n\t\t\t\t\t$bracketDepth--;\n\t\t\t\t} elseif ($char === ' ' && $bracketDepth === 0) {\n\t\t\t\t\t$typeEnd = $j;\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ($typeEnd !== null) {\n\t\t\t\t$content = substr($content, 0, $typeEnd);\n\t\t\t}\n\n\t\t\treturn $content;\n\t\t}\n\n\t\treturn 'mixed';\n\t}\n\n\t/**\n\t * @param array<string> $propertyHintMap\n\t * @param \\IdeHelper\\View\\Helper\\DocBlockHelper $helper\n\t *\n\t * @throws \\RuntimeException\n\t *\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function buildAnnotations(array $propertyHintMap, DocBlockHelper $helper): array {\n\t\t$virtualFields = $helper->getVirtualFields();\n\n\t\t$real = $virtual = [];\n\t\tforeach ($propertyHintMap as $name => $type) {\n\t\t\t$isVirtual = in_array($name, $virtualFields, true);\n\t\t\t$tag = $isVirtual ? PropertyReadAnnotation::TAG : PropertyAnnotation::TAG;\n\t\t\t$annotation = \"$tag {$type}\\${$name}\";\n\n\t\t\t$annotationObject = AnnotationFactory::create($tag, $type, $name);\n\t\t\tif (!$annotationObject) {\n\t\t\t\tthrow new RuntimeException('Cannot factorize annotation `' . $annotation . '`');\n\t\t\t}\n\n\t\t\tif ($isVirtual) {\n\t\t\t\t$virtual[$name] = $annotationObject;\n\t\t\t} else {\n\t\t\t\t$real[$name] = $annotationObject;\n\t\t\t}\n\t\t}\n\n\t\treturn $real + $virtual;\n\t}\n\n\t/**\n\t * Detect actual virtual fields by them being exposed as such.\n\t *\n\t * @param string $name\n\t *\n\t * @return array<string>\n\t */\n\tprotected function virtualFields(string $name): array {\n\t\t$plugin = $this->getConfig(static::CONFIG_PLUGIN);\n\t\t$className = App::className(($plugin ? $plugin . '.' : '') . $name, 'Model/Entity');\n\t\tif (!$className) {\n\t\t\treturn [];\n\t\t}\n\n\t\ttry {\n\t\t\t/** @var \\Cake\\Datasource\\EntityInterface $entity */\n\t\t\t$entity = new $className();\n\t\t} catch (Throwable $exception) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn $entity->getVirtual();\n\t}\n\n\t/**\n\t * @param string $type\n\t * @param array<string, array<string, mixed>> $useStatements\n\t * @return string\n\t */\n\tprotected function fqcnIfNeeded(string $type, array $useStatements): string {\n\t\t$types = explode('|', $type);\n\t\tforeach ($types as $key => $type) {\n\t\t\t$primitive = ['null', 'true', 'false', 'array', 'iterable', 'string', 'bool', 'float', 'int', 'object', 'callable', 'resource', 'mixed'];\n\t\t\tif (in_array($type, $primitive, true) || str_starts_with($type, '\\\\')) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!isset($useStatements[$type])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$types[$key] = '\\\\' . $useStatements[$type]['fullName'];\n\t\t}\n\n\t\treturn implode('|', $types);\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/HelperAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Cake\\View\\View;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse IdeHelper\\Annotator\\Traits\\HelperTrait;\nuse IdeHelper\\Utility\\App;\nuse RuntimeException;\nuse Throwable;\n\nclass HelperAnnotator extends AbstractAnnotator {\n\n\tuse HelperTrait;\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\tif (!str_ends_with($name, 'Helper')) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$name = substr($name, 0, -6);\n\t\t$plugin = $this->getConfig(static::CONFIG_PLUGIN);\n\n\t\t/** @phpstan-var class-string<object>|null $className */\n\t\t$className = App::className(($plugin ? $plugin . '.' : '') . $name, 'View/Helper', 'Helper');\n\t\tif (!$className) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($this->_isAbstract($className)) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\t$helper = new $className(new View());\n\t\t} catch (Throwable $e) {\n\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t$this->_io->warn('   Skipping helper annotations: ' . $e->getMessage());\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t/** @uses \\Cake\\View\\Helper::helpers */\n\t\t$helperMap = $this->invokeProperty($helper, 'helpers');\n\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\n\t\t$annotations = $this->getHelperAnnotations($helperMap);\n\n\t\treturn $this->annotateContent($path, $content, $annotations);\n\t}\n\n\t/**\n\t * @param array<string, array<string, mixed>> $helperMap\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function getHelperAnnotations(array $helperMap): array {\n\t\tif (!$helperMap) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$helperAnnotations = [];\n\t\tforeach ($helperMap as $helper => $config) {\n\t\t\t$className = $this->findClassName($config['className'] ?? $helper, !$this->getConfig(static::CONFIG_PLUGIN));\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$helperAnnotations[] = AnnotationFactory::createOrFail(PropertyAnnotation::TAG, '\\\\' . $className, '$' . $helper);\n\t\t}\n\n\t\treturn $helperAnnotations;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ModelAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Database\\Schema\\TableSchemaInterface;\nuse Cake\\Datasource\\EntityInterface;\nuse Cake\\Datasource\\ResultSetInterface;\nuse Cake\\ORM\\AssociationCollection;\nuse Cake\\ORM\\Table;\nuse Cake\\ORM\\TableRegistry;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\ExtendsAnnotation;\nuse IdeHelper\\Annotation\\MixinAnnotation;\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\GenericString;\nuse ReflectionClass;\nuse RuntimeException;\nuse Throwable;\n\nclass ModelAnnotator extends AbstractAnnotator {\n\n\tpublic const CLASS_TABLE = Table::class;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TABLE_BEHAVIORS = 'tableBehaviors';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const BEHAVIOR_MIXIN = 'mixin';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const BEHAVIOR_EXTENDS = 'extends';\n\n\t/**\n\t * @var array<string, array<string, string>>\n\t */\n\tprotected array $_cache = [];\n\n\t/**\n\t * @param \\IdeHelper\\Console\\Io $io\n\t * @param array<string, mixed> $config\n\t */\n\tpublic function __construct(Io $io, array $config) {\n\t\tparent::__construct($io, $config);\n\n\t\t/** @var string|bool|null $tableBehaviors */\n\t\t$tableBehaviors = Configure::read('IdeHelper.tableBehaviors');\n\t\tif ($tableBehaviors === true) {\n\t\t\t$tableBehaviors = [\n\t\t\t\tstatic::BEHAVIOR_MIXIN,\n\t\t\t\tstatic::BEHAVIOR_EXTENDS,\n\t\t\t];\n\t\t} elseif ($tableBehaviors === false) {\n\t\t\t$tableBehaviors = [];\n\t\t} elseif ($tableBehaviors === null) {\n\t\t\t$tableBehaviors = [\n\t\t\t\tstatic::BEHAVIOR_MIXIN,\n\t\t\t];\n\t\t\tif (version_compare(Configure::version(), '5.2.3', '>=')) {\n\t\t\t\t$tableBehaviors[] = static::BEHAVIOR_EXTENDS;\n\t\t\t}\n\t\t}\n\n\t\t$this->setConfig(static::TABLE_BEHAVIORS, (array)$tableBehaviors);\n\t}\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$className = pathinfo($path, PATHINFO_FILENAME);\n\t\tif ($className === 'Table' || !str_ends_with($className, 'Table')) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$modelName = substr($className, 0, -5);\n\t\t$plugin = $this->getConfig(static::CONFIG_PLUGIN);\n\n\t\t$tableName = $plugin ? ($plugin . '.' . $modelName) : $modelName;\n\t\ttry {\n\t\t\t/** @phpstan-var class-string<object> $tableClass */\n\t\t\t$tableClass = App::classNameOrFail($tableName, 'Model/Table', 'Table');\n\t\t} catch (Throwable $e) {\n\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t$this->_io->warn('   Skipping table and entity: ' . $e->getMessage());\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($this->_isAbstract($tableClass)) {\n\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t$this->_io->warn('   Skipping table and entity: Abstract class');\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$tableReflection = new ReflectionClass($tableClass);\n\t\tif (!$tableReflection->isInstantiable()) {\n\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t$this->_io->warn('   Skipping table and entity: Not instantiable');\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\t$table = TableRegistry::getTableLocator()->get($tableName);\n\t\t\t$schema = $table->getSchema();\n\t\t\t$behaviors = $this->getBehaviors($table);\n\t\t} catch (Throwable $e) {\n\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t$this->_io->warn('   Skipping table and entity: ' . $e->getMessage());\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$tableAssociations = $table->associations();\n\t\ttry {\n\t\t\t$associations = $this->getAssociations($tableAssociations);\n\t\t} catch (Throwable $e) {\n\t\t\tif ($this->getConfig(static::CONFIG_VERBOSE)) {\n\t\t\t\t$this->_io->warn('   Skipping associations: ' . $e->getMessage());\n\t\t\t}\n\t\t\t$associations = [];\n\t\t}\n\n\t\t$entityClassName = $table->getEntityClass();\n\t\t$entityName = substr($entityClassName, strrpos($entityClassName, '\\\\') + 1);\n\n\t\t$parentClass = (string)get_parent_class($table);\n\t\t$resTable = $this->table($path, $entityName, $associations, $behaviors, $parentClass, $entityClassName);\n\t\t$resEntity = $this->entity($entityClassName, $entityName, $schema, $tableAssociations);\n\n\t\treturn $resTable || $resEntity;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $entityName\n\t * @param array<string, mixed> $associations\n\t * @param array<string> $behaviors\n\t * @param string $parentClass\n\t * @param string $entityClass\n\t * @return bool\n\t */\n\tprotected function table(string $path, string $entityName, array $associations, array $behaviors, string $parentClass, string $entityClass = ''): bool {\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\n\t\t$behaviors += $this->parseLoadedBehaviors($content);\n\t\t$annotations = $this->buildAnnotations($associations, $entityName, $behaviors, $parentClass, $entityClass);\n\n\t\treturn $this->annotateContent($path, $content, $annotations);\n\t}\n\n\t/**\n\t * @param array<string, mixed> $associations\n\t * @param string $entity\n\t * @param array<string> $behaviors\n\t * @param string $parentClass\n\t * @param string $entityClass\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function buildAnnotations(array $associations, string $entity, array $behaviors, string $parentClass, string $entityClass = ''): array {\n\t\t$namespace = $this->getConfig(static::CONFIG_NAMESPACE);\n\t\t$annotations = [];\n\t\tforeach ($associations as $type => $assocs) {\n\t\t\tforeach ($assocs as $name => $className) {\n\t\t\t\tif (Configure::read('IdeHelper.assocsAsGenerics') === true) {\n\t\t\t\t\t$annotations[] = \"@property \\\\{$type}<\\\\{$className}> \\${$name}\";\n\t\t\t\t} else {\n\t\t\t\t\t$annotations[] = \"@property \\\\{$className}&\\\\{$type} \\${$name}\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t$fullClassName = \"$namespace\\\\Model\\\\Entity\\\\$entity\";\n\t\tif (class_exists($fullClassName)) {\n\t\t\t$fullClassName = '\\\\' . $fullClassName;\n\t\t\t$fullClassNameCollection = GenericString::generate($fullClassName);\n\t\t\t$entityInterface = '\\\\' . EntityInterface::class;\n\t\t\t$resultSetInterfaceCollection = GenericString::generate($fullClassName, '\\\\' . ResultSetInterface::class);\n\n\t\t\tif (Configure::read('IdeHelper.concreteEntitiesInParam')) {\n\t\t\t\t$entityInterface = $fullClassName;\n\t\t\t}\n\n\t\t\t$generics = Configure::read('IdeHelper.genericsInParam');\n\t\t\t$detailed = $generics === 'detailed';\n\t\t\t$dataType = 'array';\n\t\t\t$dataListType = 'array';\n\t\t\t$optionsType = 'array';\n\t\t\t$iterable = 'iterable';\n\t\t\t$finderType = 'array|string';\n\t\t\t$findOrCreateSearchType = '\\Cake\\ORM\\Query\\SelectQuery|callable|array';\n\t\t\tif ($generics) {\n\t\t\t\t$dataType = $detailed ? 'array<string, mixed>' : 'array<mixed>';\n\t\t\t\t$dataListType = $detailed ? 'array<array<string, mixed>>' : 'array<mixed>';\n\t\t\t\t$optionsType = 'array<string, mixed>';\n\t\t\t\t// Detailed mode always narrows iterables to the concrete entity — a UsersTable only ever handles User entities.\n\t\t\t\t$iterableEntity = $detailed ? $fullClassName : $entityInterface;\n\t\t\t\t$iterable = \"iterable<{$iterableEntity}>\";\n\t\t\t}\n\t\t\tif ($detailed) {\n\t\t\t\t$finderType = 'array<string, mixed>|string';\n\t\t\t\t$findOrCreateSearchType = \"\\Cake\\ORM\\Query\\SelectQuery<{$fullClassName}>|callable|array<string, mixed>\";\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * Copied from Bake plugin's DocBlockHelper\n\t\t\t *\n\t\t\t * @link \\Bake\\View\\Helper\\DocBlockHelper::buildTableAnnotations()\n\t\t\t */\n\t\t\t$annotations[] = \"@method {$fullClassName} newEmptyEntity()\";\n\t\t\t$annotations[] = \"@method {$fullClassName} newEntity({$dataType} \\$data, {$optionsType} \\$options = [])\";\n\t\t\t$annotations[] = \"@method {$fullClassNameCollection} newEntities({$dataListType} \\$data, {$optionsType} \\$options = [])\";\n\n\t\t\t$annotations[] = \"@method {$fullClassName} get(mixed \\$primaryKey, {$finderType} \\$finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null \\$cache = null, \\Closure|string|null \\$cacheKey = null, mixed ...\\$args)\";\n\t\t\tif (Configure::read('IdeHelper.tableEntityQuery')) {\n\t\t\t\t$annotations[] = \"@method \\Cake\\ORM\\Query\\SelectQuery<{$fullClassName}> find(string \\$type = 'all', mixed ...\\$args)\";\n\t\t\t}\n\t\t\t$annotations[] = \"@method {$fullClassName} findOrCreate({$findOrCreateSearchType} \\$search, ?callable \\$callback = null, {$optionsType} \\$options = [])\";\n\n\t\t\t$annotations[] = \"@method {$fullClassName} patchEntity({$entityInterface} \\$entity, {$dataType} \\$data, {$optionsType} \\$options = [])\";\n\t\t\t$annotations[] = \"@method {$fullClassNameCollection} patchEntities({$iterable} \\$entities, {$dataListType} \\$data, {$optionsType} \\$options = [])\";\n\n\t\t\t$annotations[] = \"@method {$fullClassName}|false save({$entityInterface} \\$entity, {$optionsType} \\$options = [])\";\n\t\t\t$annotations[] = \"@method {$fullClassName} saveOrFail({$entityInterface} \\$entity, {$optionsType} \\$options = [])\";\n\n\t\t\t$annotations[] = \"@method {$resultSetInterfaceCollection}|false saveMany({$iterable} \\$entities, {$optionsType} \\$options = [])\";\n\t\t\t$annotations[] = \"@method {$resultSetInterfaceCollection} saveManyOrFail({$iterable} \\$entities, {$optionsType} \\$options = [])\";\n\n\t\t\t$annotations[] = \"@method {$resultSetInterfaceCollection}|false deleteMany({$iterable} \\$entities, {$optionsType} \\$options = [])\";\n\t\t\t$annotations[] = \"@method {$resultSetInterfaceCollection} deleteManyOrFail({$iterable} \\$entities, {$optionsType} \\$options = [])\";\n\t\t}\n\n\t\t// Make replaceable via parsed object\n\t\t$result = [];\n\t\tforeach ($annotations as $annotation) {\n\t\t\t$annotationObject = AnnotationFactory::createFromString($annotation);\n\t\t\tif (!$annotationObject) {\n\t\t\t\tthrow new RuntimeException('Cannot factorize annotation ' . $annotation);\n\t\t\t}\n\n\t\t\t$result[] = $annotationObject;\n\t\t}\n\n\t\t$result = $this->addBehaviorMixins($result, $behaviors);\n\t\t$result = $this->addBehaviorExtends($result, $behaviors, $parentClass, $entityClass);\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param string $entityClass\n\t * @param string $entityName\n\t * @param \\Cake\\Database\\Schema\\TableSchemaInterface $schema\n\t * @param \\Cake\\ORM\\AssociationCollection $associations\n\t *\n\t * @return bool\n\t */\n\tprotected function entity(string $entityClass, string $entityName, TableSchemaInterface $schema, AssociationCollection $associations): bool {\n\t\t$plugin = $this->getConfig(static::CONFIG_PLUGIN);\n\t\t$entityPaths = AppPath::get('Model/Entity', $plugin);\n\t\t$entityPath = null;\n\t\twhile ($entityPaths) {\n\t\t\t$pathTmp = array_shift($entityPaths);\n\t\t\t$pathTmp = str_replace('\\\\', DS, $pathTmp);\n\t\t\tif (file_exists($pathTmp . $entityName . '.php')) {\n\t\t\t\t$entityPath = $pathTmp . $entityName . '.php';\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!$entityPath) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$file = pathinfo($entityPath, PATHINFO_BASENAME);\n\t\t$this->_io->verbose('   ' . $file);\n\n\t\t$annotator = $this->getEntityAnnotator($entityClass, $schema, $associations);\n\t\t$annotator->annotate($entityPath);\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @return array<string>\n\t */\n\tprotected function parseLoadedBehaviors(string $content): array {\n\t\tpreg_match_all('/\\$this->addBehavior\\(\\'([a-z.\\/]+)\\'/i', $content, $matches);\n\t\tif (empty($matches[1])) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$behaviors = array_unique($matches[1]);\n\n\t\t$result = [];\n\t\tforeach ($behaviors as $behavior) {\n\t\t\t[, $behaviorName] = pluginSplit($behavior);\n\t\t\t$result[$behaviorName] = $behavior;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param \\Cake\\ORM\\AssociationCollection $tableAssociations\n\t * @return array<string, array<string, string>>\n\t */\n\tprotected function getAssociations(AssociationCollection $tableAssociations): array {\n\t\t$associations = [];\n\t\tforeach ($tableAssociations->keys() as $key) {\n\t\t\t$association = $tableAssociations->get($key);\n\t\t\tif (!$association) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$type = get_class($association);\n\n\t\t\t[, $name] = pluginSplit($association->getAlias());\n\t\t\t$table = $association->getClassName() ?: $association->getAlias();\n\t\t\t$className = App::className($table, 'Model/Table', 'Table') ?: static::CLASS_TABLE;\n\n\t\t\t$associations[$type][$name] = $className;\n\t\t}\n\n\t\treturn $associations;\n\t}\n\n\t/**\n\t * @param \\Cake\\ORM\\Table $table\n\t * @return array<string>\n\t */\n\tprotected function getBehaviors($table): array {\n\t\t$object = $table->behaviors();\n\t\t$map = $this->invokeProperty($object, '_loaded');\n\n\t\t$behaviors = $this->extractBehaviors($map);\n\t\t/** @phpstan-var class-string<object>|false $parentClass */\n\t\t$parentClass = get_parent_class($table);\n\t\tif (!$parentClass) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (isset($this->_cache[$parentClass])) {\n\t\t\t$parentBehaviors = $this->_cache[$parentClass];\n\t\t} else {\n\t\t\t$parentReflection = new ReflectionClass($parentClass);\n\t\t\tif (!$parentReflection->isInstantiable()) {\n\t\t\t\treturn $behaviors;\n\t\t\t}\n\n\t\t\t/** @var \\Cake\\ORM\\Table $parent */\n\t\t\t$parent = new $parentClass();\n\n\t\t\t$object = $parent->behaviors();\n\t\t\t$map = $this->invokeProperty($object, '_loaded');\n\t\t\t$this->_cache[$parentClass] = $parentBehaviors = $this->extractBehaviors($map);\n\t\t}\n\n\t\treturn array_diff_key($behaviors, $parentBehaviors);\n\t}\n\n\t/**\n\t * @param array<string> $map\n\t * @return array<string>\n\t */\n\tprotected function extractBehaviors(array $map): array {\n\t\t$result = [];\n\t\t/** @var object|string $behavior */\n\t\tforeach ($map as $name => $behavior) {\n\t\t\t$behaviorClassName = get_class($behavior) ?: '';\n\t\t\t$behaviorName = $this->resolveBehaviorName($behaviorClassName, $name);\n\t\t\t$pluginName = $this->resolvePluginName($behaviorClassName, $name);\n\t\t\tif ($pluginName) {\n\t\t\t\t$pluginName .= '.';\n\t\t\t}\n\t\t\t$result[$name] = $pluginName . $behaviorName;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param string $className\n\t * @param string $name\n\t * @return string|null\n\t */\n\tprotected function resolveBehaviorName(string $className, string $name): ?string {\n\t\tpreg_match('#\\\\\\\\(?:Model|ORM)\\\\\\\\Behavior\\\\\\\\(.+)Behavior$#', $className, $matches);\n\t\tif (!$matches) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn str_replace('\\\\', '/', $matches[1]);\n\t}\n\n\t/**\n\t * @param string $className\n\t * @param string $name\n\t *\n\t * @return string|null\n\t */\n\tprotected function resolvePluginName(string $className, string $name): ?string {\n\t\tif (str_starts_with($className, 'Cake\\\\ORM')) {\n\t\t\treturn '';\n\t\t}\n\t\tif (str_starts_with($className, 'App\\\\Model\\\\')) {\n\t\t\treturn '';\n\t\t}\n\n\t\tif (str_contains($name, '\\\\')) {\n\t\t\tpreg_match('#^(.+?)\\\\\\\\Model\\\\\\\\Behavior\\\\\\\\#', $className, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t} else {\n\t\t\tpreg_match('#^(.+?)\\\\\\\\Model\\\\\\\\Behavior\\\\\\\\(.+)Behavior$#', $className, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\n\t\treturn str_replace('\\\\', '/', $matches[1]);\n\t}\n\n\t/**\n\t * @param string $entityClass\n\t * @param \\Cake\\Database\\Schema\\TableSchemaInterface $schema\n\t * @param \\Cake\\ORM\\AssociationCollection $associations\n\t * @return \\IdeHelper\\Annotator\\AbstractAnnotator\n\t */\n\tprotected function getEntityAnnotator(string $entityClass, TableSchemaInterface $schema, AssociationCollection $associations): AbstractAnnotator {\n\t\t$class = EntityAnnotator::class;\n\t\t/** @phpstan-var array<class-string<\\IdeHelper\\Annotator\\AbstractAnnotator>> $tasks */\n\t\t$tasks = (array)Configure::read('IdeHelper.annotators');\n\t\tif (isset($tasks[$class])) {\n\t\t\t$class = $tasks[$class];\n\t\t}\n\n\t\treturn new $class($this->_io, ['class' => $entityClass, 'schema' => $schema, 'associations' => $associations] + $this->getConfig());\n\t}\n\n\t/**\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $result\n\t * @param array<string> $behaviors\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function addBehaviorMixins(array $result, array $behaviors): array {\n\t\tif (!in_array(static::BEHAVIOR_MIXIN, $this->_config[static::TABLE_BEHAVIORS], true)) {\n\t\t\treturn $result;\n\t\t}\n\n\t\tforeach ($behaviors as $behavior) {\n\t\t\t$className = App::className($behavior, 'Model/Behavior', 'Behavior');\n\t\t\tif (!$className) {\n\t\t\t\t$className = App::className($behavior, 'ORM/Behavior', 'Behavior');\n\t\t\t}\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$result[] = AnnotationFactory::createOrFail(MixinAnnotation::TAG, \"\\\\{$className}\");\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $result\n\t * @param array<string> $behaviors\n\t * @param string $parentClass\n\t * @param string $entityClass\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function addBehaviorExtends(array $result, array $behaviors, string $parentClass, string $entityClass = ''): array {\n\t\tif (!in_array(static::BEHAVIOR_EXTENDS, $this->_config[static::TABLE_BEHAVIORS], true)) {\n\t\t\treturn $result;\n\t\t}\n\n\t\t$list = [];\n\t\tforeach ($behaviors as $name => $fullName) {\n\t\t\t$className = App::className($fullName, 'Model/Behavior', 'Behavior');\n\t\t\tif (!$className) {\n\t\t\t\t$className = App::className($fullName, 'ORM/Behavior', 'Behavior');\n\t\t\t}\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$list[] = $name . ': \\\\' . $className;\n\t\t}\n\n\t\t$entityTemplate = $this->supportsEntityTemplate() && $entityClass ? ', \\\\' . ltrim($entityClass, '\\\\') : '';\n\n\t\tif (!$list && !$entityTemplate) {\n\t\t\treturn $result;\n\t\t}\n\n\t\tsort($list);\n\n\t\t$list = implode(', ', $list);\n\n\t\tif (!$parentClass) {\n\t\t\t$parentClass = '\\\\Cake\\\\ORM\\\\Table';\n\t\t}\n\t\t$result[] = AnnotationFactory::createOrFail(ExtendsAnnotation::TAG, '\\\\' . $parentClass . '<array{' . $list . '}' . $entityTemplate . '>');\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * Whether `\\Cake\\ORM\\Table` declares a second `TEntity` template parameter (CakePHP 5.4+).\n\t *\n\t * @return bool\n\t */\n\tprotected function supportsEntityTemplate(): bool {\n\t\treturn version_compare(Configure::version(), '5.4.0', '>=');\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/Template/VariableExtractor.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\Template;\n\nuse PHP_CodeSniffer\\Files\\File;\nuse PHP_CodeSniffer\\Util\\Tokens;\n\n/**\n * Extracts variables from CakePHP PHP templates using token list of CS File object.\n */\nclass VariableExtractor {\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @return array<string, mixed>\n\t */\n\tpublic function extract(File $file): array {\n\t\t$vars = $this->collect($file);\n\n\t\t$result = [];\n\t\tforeach ($vars as $var) {\n\t\t\t/** @var string $name */\n\t\t\t$name = $var['name'];\n\t\t\tif (!isset($result[$name])) {\n\t\t\t\t$result[$name] = $var;\n\t\t\t\t$result[$name]['vars'][] = $var;\n\t\t\t}\n\n\t\t\tif ($var['excludeReason']) {\n\t\t\t\t$result[$name]['excludeReason'] = $var['excludeReason'];\n\t\t\t}\n\t\t\tif ($var['type'] && empty($result[$name]['type'])) {\n\t\t\t\t$result[$name]['type'] = $var['type'];\n\t\t\t}\n\t\t\tif ($var['type'] && !empty($result[$name]['type']) && $var['type'] !== $result[$name]['type']) {\n\t\t\t\t$result[$name]['type'] = 'mixed';\n\t\t\t}\n\t\t}\n\n\t\tksort($result);\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t *\n\t * @return array<array<string, mixed>>\n\t */\n\tprotected function collect(File $file): array {\n\t\t$tokens = $file->getTokens();\n\n\t\t$vars = [];\n\t\tforeach ($tokens as $i => $token) {\n\t\t\tif ($token['code'] !== T_VARIABLE) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ($token['content'] === '$this') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$var = $this->getVar($file, $token, $i);\n\t\t\tif (!$var) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$vars[$i] = $var;\n\t\t}\n\n\t\tforeach ($tokens as $i => $token) {\n\t\t\tif ($token['code'] !== T_DOUBLE_QUOTED_STRING) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$varsFound = $this->getVarsFromString($file, $token, $i);\n\t\t\tforeach ($varsFound as $var) {\n\t\t\t\t$vars[] = $var;\n\t\t\t}\n\t\t}\n\n\t\tforeach ($tokens as $i => $token) {\n\t\t\tif ($token['code'] !== T_STRING || strtolower($token['content']) !== 'compact') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip method calls like $obj->compact() or Class::compact()\n\t\t\t$prevIndex = $file->findPrevious(Tokens::$emptyTokens, $i - 1, null, true, null, true);\n\t\t\tif ($prevIndex !== false && in_array($tokens[$prevIndex]['code'], [T_OBJECT_OPERATOR, T_DOUBLE_COLON], true)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$varsFound = $this->getVarsFromCompact($file, $i);\n\t\t\tforeach ($varsFound as $var) {\n\t\t\t\t$vars[] = $var;\n\t\t\t}\n\t\t}\n\n\t\treturn $vars;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<string, mixed> $token\n\t * @param int $index\n\t * @return array<string, mixed>\n\t */\n\tprotected function getVar(File $file, array $token, int $index): array {\n\t\t$variable = substr($token['content'], 1);\n\n\t\t$result = [\n\t\t\t'name' => $variable,\n\t\t\t'index' => $index,\n\t\t\t'type' => null,\n\t\t\t'excludeReason' => null,\n\t\t\t'context' => $token,\n\t\t];\n\t\t$type = $this->getVarType($file, $result);\n\t\t$result['type'] = $type;\n\t\t$excludeReason = $this->getExcludeReason($file, $result);\n\t\t$result['excludeReason'] = $excludeReason;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * Guesses the variable type based on PHP token elements before or after.\n\t *\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<string, mixed> $result\n\t * @return string|null\n\t */\n\tprotected function getVarType(File $file, array $result): ?string {\n\t\t$tokens = $file->getTokens();\n\n\t\t$nextIndex = $file->findNext(Tokens::$emptyTokens, $result['index'] + 1, $result['index'] + 3, true, null, true);\n\t\tif ($nextIndex && $tokens[$nextIndex]['code'] === T_OBJECT_OPERATOR) {\n\t\t\treturn 'object';\n\t\t}\n\n\t\tif ($nextIndex && $tokens[$nextIndex]['code'] === T_OPEN_SQUARE_BRACKET) {\n\t\t\treturn 'array';\n\t\t}\n\n\t\t$prevIndex = $file->findPrevious(Tokens::$emptyTokens, $result['index'] - 1, $result['index'] - 3, true, null, true);\n\t\tif ($prevIndex && in_array($tokens[$prevIndex]['code'], [T_ECHO, T_OPEN_TAG_WITH_ECHO, T_STRING_CONCAT], true)) {\n\t\t\tif ($nextIndex && in_array($tokens[$nextIndex]['code'], [T_SEMICOLON, T_STRING_CONCAT, T_CLOSE_TAG], true)) {\n\t\t\t\treturn 'string';\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<string, mixed> $result\n\t * @return string|null\n\t */\n\tprotected function getExcludeReason(File $file, array $result): ?string {\n\t\tif ($this->isLoopVar($file, $result)) {\n\t\t\treturn 'Declared in loop';\n\t\t}\n\t\tif ($this->isTryCatchVar($file, $result)) {\n\t\t\treturn 'Try catch';\n\t\t}\n\t\tif ($this->isAnonymousFunctionParameter($file, $result)) {\n\t\t\treturn 'Anonymous function parameter';\n\t\t}\n\n\t\tif ($this->isAssignment($file, $result)) {\n\t\t\treturn 'Assignment';\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<string, mixed> $result\n\t * @return bool\n\t */\n\tprotected function isLoopVar(File $file, array $result): bool {\n\t\t$tokens = $file->getTokens();\n\n\t\t$prevIndex = $file->findPrevious(Tokens::$emptyTokens, $result['index'] - 1, $result['index'] - 3, true, null, true);\n\t\tif ($prevIndex && $tokens[$prevIndex]['code'] === T_AS) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif ($prevIndex && $tokens[$prevIndex]['code'] === T_DOUBLE_ARROW && $this->isInLoop($file, $result, $prevIndex)) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<string, mixed> $result\n\t * @param int $assignmentIndex\n\t *\n\t * @return bool\n\t */\n\tprotected function isInLoop(File $file, array $result, int $assignmentIndex): bool {\n\t\tif (empty($result['context']['nested_parenthesis'])) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$startIndex = null;\n\t\tforeach ($result['context']['nested_parenthesis'] as $key => $unused) {\n\t\t\t$startIndex = $key;\n\n\t\t\tbreak;\n\t\t}\n\n\t\treturn (bool)$file->findPrevious(T_FOREACH, $startIndex - 1, $startIndex - 3);\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<string, mixed> $result\n\t * @return bool\n\t */\n\tprotected function isTryCatchVar(File $file, array $result): bool {\n\t\tif (empty($result['context']['nested_parenthesis'])) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$startIndex = null;\n\t\tforeach ($result['context']['nested_parenthesis'] as $key => $unused) {\n\t\t\t$startIndex = $key;\n\n\t\t\tbreak;\n\t\t}\n\n\t\tif ($startIndex === null || $startIndex <= 1) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn (bool)$file->findPrevious(T_CATCH, $startIndex - 1, $startIndex - 3);\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<string, mixed> $result\n\t * @return bool\n\t */\n\tprotected function isAnonymousFunctionParameter(File $file, array $result): bool {\n\t\tif (empty($result['context']['nested_parenthesis'])) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$tokens = $file->getTokens();\n\n\t\t// Check each level of nested parenthesis\n\t\tforeach ($result['context']['nested_parenthesis'] as $openParen => $closeParen) {\n\t\t\t// Check if this is immediately preceded by 'function' or 'fn' keyword\n\t\t\tif ($openParen > 0) {\n\t\t\t\t$prevToken = $file->findPrevious(T_WHITESPACE, $openParen - 1, (int)max(0, $openParen - 2), true);\n\t\t\t\tif ($prevToken !== false && in_array($tokens[$prevToken]['code'], [T_CLOSURE, T_FN], true)) {\n\t\t\t\t\t// This variable is inside an anonymous function's parameter list\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<string, mixed> $result\n\t * @return bool\n\t */\n\tprotected function isAssignment(File $file, array $result): bool {\n\t\t$tokens = $file->getTokens();\n\n\t\t$nextIndex = $file->findNext(Tokens::$emptyTokens, $result['index'] + 1, $result['index'] + 3, true, null, true);\n\t\tif ($nextIndex && $tokens[$nextIndex]['code'] === T_EQUAL) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif ($nextIndex && $tokens[$nextIndex]['code'] === T_CLOSE_SHORT_ARRAY) {\n\t\t\t$equalIndex = $file->findNext(Tokens::$emptyTokens, $nextIndex + 1, $nextIndex + 3, true, null, true);\n\t\t\tif ($equalIndex && $tokens[$equalIndex]['code'] === T_EQUAL) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\t$prevIndex = $file->findPrevious(Tokens::$emptyTokens, $result['index'] - 1, $result['index'] - 3, true, null, true);\n\t\tif ($prevIndex === false) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor ($i = $prevIndex; $i > 0; $i--) {\n\t\t\t$testIndex = $file->findPrevious(Tokens::$emptyTokens, $i, $i - 2, true, null, true);\n\t\t\tif (!$testIndex) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ($tokens[$testIndex]['code'] !== T_COMMA && $tokens[$testIndex]['code'] !== T_VARIABLE) {\n\t\t\t\t$prevIndex = $testIndex;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif ($prevIndex && $tokens[$prevIndex]['code'] === T_OPEN_SHORT_ARRAY) {\n\t\t\t$closerIndex = $tokens[$prevIndex]['bracket_closer'];\n\t\t\t$nextIndex = $file->findNext(Tokens::$emptyTokens, $closerIndex + 1, $closerIndex + 3, true, null, true);\n\t\t\tif ($nextIndex && $tokens[$nextIndex]['code'] === T_EQUAL) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<string, mixed> $token\n\t * @param int $i\n\t * @return array<array<string, mixed>>\n\t */\n\tprotected function getVarsFromString(File $file, array $token, int $i): array {\n\n\t\tpreg_match_all('/\\$(\\{)?([a-zA-Z_][a-zA-Z0-9_]*)\\}?/', $token['content'], $matches);\n\t\tif (empty($matches[2])) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$result = [];\n\t\tforeach ($matches[2] as $variable) {\n\t\t\t// Skip $this variable as it's handled separately\n\t\t\tif ($variable === 'this') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$result[] = [\n\t\t\t\t'name' => $variable,\n\t\t\t\t'index' => $i,\n\t\t\t\t'type' => null,\n\t\t\t\t'excludeReason' => null,\n\t\t\t\t'context' => $token,\n\t\t\t];\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * Extracts variable names from compact() function calls.\n\t *\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t * @return array<array<string, mixed>>\n\t */\n\tprotected function getVarsFromCompact(File $file, int $index): array {\n\t\t$tokens = $file->getTokens();\n\n\t\t// Find the opening parenthesis\n\t\t$openParen = $file->findNext(Tokens::$emptyTokens, $index + 1, null, true, null, true);\n\t\tif ($openParen === false || $tokens[$openParen]['code'] !== T_OPEN_PARENTHESIS) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$closeParen = $tokens[$openParen]['parenthesis_closer'] ?? null;\n\t\tif ($closeParen === null) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$result = [];\n\t\tfor ($i = $openParen + 1; $i < $closeParen; $i++) {\n\t\t\tif ($tokens[$i]['code'] !== T_CONSTANT_ENCAPSED_STRING) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip array keys (string followed by =>)\n\t\t\t$nextIndex = $file->findNext(Tokens::$emptyTokens, $i + 1, $closeParen, true, null, true);\n\t\t\tif ($nextIndex !== false && $tokens[$nextIndex]['code'] === T_DOUBLE_ARROW) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Skip strings in concatenation (preceded or followed by .)\n\t\t\tif ($nextIndex !== false && $tokens[$nextIndex]['code'] === T_STRING_CONCAT) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$prevIndex = $file->findPrevious(Tokens::$emptyTokens, $i - 1, $openParen, true, null, true);\n\t\t\tif ($prevIndex !== false && $tokens[$prevIndex]['code'] === T_STRING_CONCAT) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Strip quotes from the string\n\t\t\t$variable = trim($tokens[$i]['content'], '\\'\"');\n\t\t\tif ($variable === '' || $variable === 'this') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$result[] = [\n\t\t\t\t'name' => $variable,\n\t\t\t\t'index' => $i,\n\t\t\t\t'type' => null,\n\t\t\t\t'excludeReason' => null,\n\t\t\t\t'context' => $tokens[$i],\n\t\t\t];\n\t\t}\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/TemplateAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Bake\\View\\Helper\\DocBlockHelper;\nuse Cake\\Collection\\CollectionInterface;\nuse Cake\\Core\\Configure;\nuse Cake\\Utility\\Inflector;\nuse Cake\\View\\View;\nuse IdeHelper\\Annotation\\AbstractAnnotation;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\VariableAnnotation;\nuse IdeHelper\\Annotator\\Template\\VariableExtractor;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\CollectionClass;\nuse IdeHelper\\Utility\\GenericString;\nuse PHP_CodeSniffer\\Config;\nuse PHP_CodeSniffer\\Files\\File;\nuse RuntimeException;\n\nclass TemplateAnnotator extends AbstractAnnotator {\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\n\t\t$annotations = $this->buildAnnotations($path, $content);\n\n\t\treturn $this->annotateContent($path, $content, $annotations);\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $annotations\n\t *\n\t * @return bool\n\t */\n\tprotected function annotateContent(string $path, string $content, array $annotations): bool {\n\t\tif (!count($annotations)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$file = $this->getFile($path, $content);\n\n\t\t$phpOpenTagIndex = $file->findNext(T_OPEN_TAG, 0);\n\t\tif ($phpOpenTagIndex === false) {\n\t\t\t$phpOpenTagIndex = null;\n\t\t}\n\n\t\t$needsPhpTag = $phpOpenTagIndex === null || $this->needsPhpTag($file, $phpOpenTagIndex);\n\n\t\t// Adjust position after declare statement for searching and adding new content\n\t\t$phpOpenTagIndexAdjusted = $this->checkForDeclareStatement($file, $phpOpenTagIndex);\n\n\t\t$docBlockCloseTagIndex = null;\n\t\tif ($needsPhpTag) {\n\t\t\t$phpOpenTagIndexForSearch = null;\n\t\t} else {\n\t\t\t$phpOpenTagIndexForSearch = $phpOpenTagIndexAdjusted;\n\t\t}\n\t\tif ($phpOpenTagIndexForSearch !== null) {\n\t\t\t$docBlockCloseTagIndex = $this->findExistingDocBlock($file, $phpOpenTagIndexForSearch);\n\t\t}\n\n\t\t$phpOpenTagIndex = $phpOpenTagIndexAdjusted;\n\t\tif ($needsPhpTag) {\n\t\t\t$phpOpenTagIndex = null;\n\t\t}\n\n\t\t$this->resetCounter();\n\t\tif ($docBlockCloseTagIndex && !$this->isInlineDocBlock($file, $docBlockCloseTagIndex)) {\n\t\t\t$newContent = $this->appendToExistingDocBlock($file, $docBlockCloseTagIndex, $annotations);\n\t\t} else {\n\t\t\t$newContent = $this->addNewTemplateDocBlock($file, $annotations, $phpOpenTagIndex, $docBlockCloseTagIndex);\n\t\t}\n\n\t\tif ($newContent === $content) {\n\t\t\t$this->reportSkipped($path);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!$this->getConfig('verbose')) {\n\t\t\t$this->_io->out('-> ' . str_replace(ROOT . DS, '', $path));\n\t\t}\n\n\t\t$this->displayDiff($content, $newContent);\n\t\t$this->storeFile($path, $newContent);\n\n\t\t$this->report();\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $phpOpenTagIndex\n\t * @return int|null\n\t */\n\tprotected function findExistingDocBlock(File $file, int $phpOpenTagIndex): ?int {\n\t\t$tokens = $file->getTokens();\n\n\t\t$nextIndex = $file->findNext(T_WHITESPACE, $phpOpenTagIndex + 1, null, true);\n\t\tif ($tokens[$nextIndex]['type'] !== 'T_DOC_COMMENT_OPEN_TAG') {\n\t\t\treturn null;\n\t\t}\n\n\t\t$commentCloseIndex = $tokens[$nextIndex]['comment_closer'];\n\n\t\t$tagIndex = $file->findNext(T_DOC_COMMENT_TAG, $phpOpenTagIndex + 1, $commentCloseIndex);\n\t\tif (!$tagIndex || $tokens[$tagIndex]['content'] === '@var') {\n\t\t\treturn $commentCloseIndex;\n\t\t}\n\n\t\t// Assume the first doc block is the license file doc block\n\t\twhile ($closeIndex = $this->findExistingDocBlock($file, $commentCloseIndex)) {\n\t\t\t$line = $tokens[$closeIndex]['line'];\n\t\t\t$openIndex = $tokens[$closeIndex]['comment_opener'];\n\t\t\t$nextContentIndex = $file->findNext(T_WHITESPACE, $closeIndex + 1, null, true);\n\t\t\t// This must be an inline docblock, so we need to bail\n\t\t\tif (\n\t\t\t\t$nextContentIndex\n\t\t\t\t&& $tokens[$nextContentIndex]['line'] === $line + 1\n\t\t\t\t&& $tokens[$openIndex]['line'] === $tokens[$closeIndex]['line']\n\t\t\t) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t$commentCloseIndex = $closeIndex;\n\t\t}\n\n\t\treturn $commentCloseIndex;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $annotations\n\t * @param int|null $phpOpenTagIndex\n\t * @param int|null $docBlockCloseIndex\n\t *\n\t * @throws \\RuntimeException\n\t *\n\t * @return string\n\t */\n\tprotected function addNewTemplateDocBlock(File $file, array $annotations, ?int $phpOpenTagIndex, ?int $docBlockCloseIndex): string {\n\t\t$helper = new DocBlockHelper(new View());\n\n\t\t$annotationStrings = [];\n\t\tforeach ($annotations as $key => $annotation) {\n\t\t\tif (!is_object($annotation)) {\n\t\t\t\tthrow new RuntimeException('Must be object: ' . print_r($annotation, true));\n\t\t\t}\n\t\t\t$annotationStrings[$key] = (string)$annotation;\n\t\t}\n\n\t\t$annotationString = $helper->classDescription('', '', $annotationStrings);\n\t\tif (PHP_EOL !== \"\\n\") {\n\t\t\t$annotationString = str_replace(\"\\n\", PHP_EOL, $annotationString);\n\t\t}\n\n\t\tif ($phpOpenTagIndex === null) {\n\t\t\t$annotationString = '<?php' . PHP_EOL . $annotationString . PHP_EOL . '?>';\n\t\t}\n\n\t\t$docBlock = $annotationString . PHP_EOL;\n\t\tif (!$file->getTokens()) {\n\t\t\t$this->_counter[static::COUNT_ADDED] = count($annotations);\n\n\t\t\treturn $docBlock;\n\t\t}\n\n\t\t$fixer = $this->getFixer($file);\n\t\tif ($phpOpenTagIndex === null) {\n\t\t\t$fixer->addContentBefore(0, $docBlock);\n\t\t} else {\n\t\t\t// PHPCS v4 requires a blank line after <?php tag for PSR12 compliance\n\t\t\t// PHPCS v3 does not have this requirement\n\t\t\tif ($this->isV4()) {\n\t\t\t\t$fixer->addContent($phpOpenTagIndex, PHP_EOL . $annotationString);\n\t\t\t} else {\n\t\t\t\t$fixer->addContent($phpOpenTagIndex, $docBlock);\n\t\t\t}\n\t\t}\n\n\t\t$this->_counter[static::COUNT_ADDED] = count($annotations);\n\n\t\tif ($docBlockCloseIndex && $this->isInlineDocBlockRedundant($file, $annotations, $docBlockCloseIndex)) {\n\t\t\t$tokens = $file->getTokens();\n\t\t\t$docBlockOpenIndex = $tokens[$docBlockCloseIndex]['comment_opener'];\n\t\t\tfor ($i = $docBlockCloseIndex + 1; $i >= $docBlockOpenIndex; $i--) {\n\t\t\t\t$fixer->replaceToken($i, '');\n\t\t\t}\n\n\t\t\t$this->_counter[static::COUNT_ADDED]--;\n\t\t}\n\n\t\treturn $fixer->getContents();\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $phpOpenTagIndex\n\t * @return bool\n\t */\n\tprotected function needsPhpTag(File $file, int $phpOpenTagIndex): bool {\n\t\t$needsPhpTag = true;\n\n\t\t$tokens = $file->getTokens();\n\n\t\tif ($phpOpenTagIndex === 0 || ($phpOpenTagIndex > 0 && $this->isFirstContent($tokens, $phpOpenTagIndex))) {\n\t\t\t$needsPhpTag = false;\n\t\t}\n\t\tif ($needsPhpTag) {\n\t\t\treturn true;\n\t\t}\n\n\t\t$nextIndex = $file->findNext(T_WHITESPACE, $phpOpenTagIndex + 1, null, true);\n\t\tif ($tokens[$nextIndex]['code'] === T_DECLARE) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($tokens[$nextIndex]['line'] === $tokens[$phpOpenTagIndex]['line']) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn $needsPhpTag;\n\t}\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return bool\n\t */\n\tprotected function needsViewAnnotation(string $content): bool {\n\t\tif (Configure::read('IdeHelper.preemptive')) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (preg_match('/\\$this->/', $content)) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (preg_match('/<\\?/', $content)) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @return \\IdeHelper\\Annotation\\VariableAnnotation\n\t */\n\tprotected function getViewAnnotation() {\n\t\t$className = Configure::read('IdeHelper.viewClass');\n\t\tif (!$className) {\n\t\t\t$className = (Configure::read('App.namespace') ?: 'App') . '\\View\\AppView';\n\t\t}\n\t\tif (!class_exists($className)) {\n\t\t\t$className = 'Cake\\View\\View';\n\t\t}\n\n\t\t/** @var \\IdeHelper\\Annotation\\VariableAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createOrFail(VariableAnnotation::TAG, '\\\\' . $className, '$this');\n\n\t\treturn $annotation;\n\t}\n\n\t/**\n\t * Preserves existing view annotations if they reference a valid View subclass.\n\t *\n\t * @param \\IdeHelper\\Annotation\\AbstractAnnotation $annotation\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $existingAnnotations\n\t * @return bool\n\t */\n\tprotected function allowsReplacing(AbstractAnnotation $annotation, array &$existingAnnotations): bool {\n\t\tforeach ($existingAnnotations as $key => $existingAnnotation) {\n\t\t\tif (!$existingAnnotation->matches($annotation)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Check if this is a $this view annotation with a valid View class\n\t\t\tif (\n\t\t\t\t$existingAnnotation instanceof VariableAnnotation\n\t\t\t\t&& $existingAnnotation->getVariable() === '$this'\n\t\t\t) {\n\t\t\t\t$existingType = ltrim($existingAnnotation->getType(), '\\\\');\n\t\t\t\tif (class_exists($existingType) && is_subclass_of($existingType, View::class)) {\n\t\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Fall back to parent behavior for description check\n\t\t\tif ($existingAnnotation->getDescription() !== '') {\n\t\t\t\tunset($existingAnnotations[$key]);\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param array<array<string, mixed>> $tokens\n\t * @param int $phpOpenTagIndex\n\t *\n\t * @return bool\n\t */\n\tprotected function isFirstContent(array $tokens, int $phpOpenTagIndex): bool {\n\t\tfor ($i = $phpOpenTagIndex - 1; $i >= 0; $i--) {\n\t\t\tif ($tokens[$i]['type'] !== T_INLINE_HTML) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (trim($tokens[$i]['content']) !== '') {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param array<string, array<string, mixed>> $variables\n\t *\n\t * @return array<string, mixed>\n\t */\n\tprotected function getEntityAnnotations(string $content, array $variables): array {\n\t\t$loopEntityAnnotations = $this->parseLoopEntities($content);\n\t\t$formEntityAnnotations = $this->parseFormEntities($content);\n\t\t$entityAnnotations = $this->parseEntities($content);\n\n\t\t$entityAnnotations = $loopEntityAnnotations + $formEntityAnnotations + $entityAnnotations;\n\n\t\tforeach ($entityAnnotations as $name => $entityAnnotation) {\n\t\t\tif (!empty($variables[$name]) && $variables[$name]['excludeReason']) {\n\t\t\t\tunset($entityAnnotations[$name]);\n\t\t\t}\n\t\t}\n\n\t\treturn $entityAnnotations;\n\t}\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return array<string, mixed>\n\t */\n\tprotected function parseFormEntities(string $content): array {\n\t\tpreg_match_all('/\\$this->Form->create\\(\\$(\\w+)\\W/i', $content, $matches);\n\t\tif (empty($matches[1])) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$result = [];\n\n\t\t/** @var array<string> $entities */\n\t\t$entities = array_unique($matches[1]);\n\t\tforeach ($entities as $entity) {\n\t\t\t$entityName = Inflector::camelize(Inflector::underscore($entity));\n\n\t\t\t$className = App::className(($this->getConfig(static::CONFIG_PLUGIN) ? $this->getConfig(static::CONFIG_PLUGIN) . '.' : '') . $entityName, 'Model/Entity');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$annotation = AnnotationFactory::createOrFail(VariableAnnotation::TAG, '\\\\' . $className, '$' . $entity);\n\n\t\t\t$result[$entity] = $annotation;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return array<string, mixed>\n\t */\n\tprotected function parseLoopEntities(string $content): array {\n\t\tpreg_match_all('/\\bforeach \\(\\$([a-z]+) as \\$([a-z]+)\\)/i', $content, $matches);\n\t\tif (empty($matches[2])) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$result = [];\n\n\t\t$entities = $matches[2];\n\t\tforeach ($entities as $key => $entity) {\n\t\t\tif (Inflector::pluralize($entity) !== $matches[1][$key]) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$entityName = Inflector::camelize(Inflector::underscore($entity));\n\n\t\t\t$className = App::className(($this->getConfig(static::CONFIG_PLUGIN) ? $this->getConfig(static::CONFIG_PLUGIN) . '.' : '') . $entityName, 'Model/Entity');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$resultKey = $matches[1][$key];\n\t\t\t$annotation = GenericString::generate('\\\\' . $className);\n\t\t\tif (Configure::read('IdeHelper.templateCollectionObject') !== false) {\n\t\t\t\t$collectionClass = CollectionClass::name('\\\\' . CollectionInterface::class);\n\t\t\t\t$collectionType = Configure::read('IdeHelper.templateCollectionObject');\n\t\t\t\tif (Configure::read('IdeHelper.objectAsGenerics') === true && $collectionType !== 'iterable') {\n\t\t\t\t\t$annotation .= '|' . GenericString::generate('\\\\' . $className, $collectionClass);\n\t\t\t\t} else {\n\t\t\t\t\t$annotation = GenericString::generate('\\\\' . $className, $collectionClass);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$result[$resultKey] = AnnotationFactory::createOrFail(VariableAnnotation::TAG, $annotation, '$' . $matches[1][$key]);\n\t\t\t// We do not need the singular then\n\t\t\t$result[$entity] = null;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return array<string, mixed>\n\t */\n\tprotected function parseEntities(string $content): array {\n\t\tpreg_match_all('/\\$([a-z]+)->[a-z]+/i', $content, $matches);\n\t\tif (empty($matches[1])) {\n\t\t\treturn [];\n\t\t}\n\t\t/** @var array<string> $variableNames */\n\t\t$variableNames = array_unique($matches[1]);\n\n\t\t$result = [];\n\n\t\tforeach ($variableNames as $entity) {\n\t\t\tif ($entity === 'this') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$entityName = Inflector::camelize(Inflector::underscore($entity));\n\n\t\t\t$className = App::className(($this->getConfig(static::CONFIG_PLUGIN) ? $this->getConfig(static::CONFIG_PLUGIN) . '.' : '') . $entityName, 'Model/Entity');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$result[$entity] = AnnotationFactory::createOrFail(VariableAnnotation::TAG, '\\\\' . $className, '$' . $entity);\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<\\IdeHelper\\Annotation\\AbstractAnnotation> $annotations\n\t * @param int $docBlockCloseIndex\n\t *\n\t * @return bool\n\t */\n\tprotected function isInlineDocBlockRedundant(File $file, array $annotations, int $docBlockCloseIndex): bool {\n\t\t$existingAnnotations = $this->parseExistingAnnotations($file, $docBlockCloseIndex);\n\n\t\tforeach ($existingAnnotations as $existingAnnotation) {\n\t\t\tforeach ($annotations as $annotation) {\n\t\t\t\tif ($existingAnnotation->build() === $annotation->build()) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t *\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function buildAnnotations(string $path, string $content): array {\n\t\t$annotations = [];\n\n\t\t$needsAnnotation = $this->needsViewAnnotation($content);\n\t\tif ($needsAnnotation) {\n\t\t\t$annotations[] = $this->getViewAnnotation();\n\t\t}\n\n\t\t$variables = $this->getTemplateVariables($path, $content);\n\n\t\t$entityAnnotations = $this->getEntityAnnotations($content, $variables);\n\t\tforeach ($variables as $name => $variable) {\n\t\t\tif ($variable['excludeReason'] || isset($entityAnnotations[$name])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (Configure::read('IdeHelper.autoCollect') === false) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$annotations[] = $this->getVariableAnnotation($variable);\n\t\t}\n\n\t\t/** @var \\IdeHelper\\Annotation\\AbstractAnnotation|null $entityAnnotation */\n\t\tforeach ($entityAnnotations as $entityAnnotation) {\n\t\t\tif (!$entityAnnotation) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$annotations[] = $entityAnnotation;\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * Gets all template variables and a bit about their scope/context\n\t * - type (if detected, e.g. string, object)\n\t * - excludeReason (if detected as excludable, e.g. inside local assignment/loop)\n\t *\n\t * @param string $path\n\t * @param string $content\n\t *\n\t * @return array<string, mixed>\n\t */\n\tprotected function getTemplateVariables(string $path, string $content): array {\n\t\t$file = $this->getFile($path, $content);\n\n\t\t$class = Configure::read('IdeHelper.variableExtractor') ?: VariableExtractor::class;\n\t\t/** @var \\IdeHelper\\Annotator\\Template\\VariableExtractor $extractor */\n\t\t$extractor = new $class();\n\n\t\t$variables = $extractor->extract($file);\n\t\t/** @var array<string> $blacklist */\n\t\t$blacklist = (array)Configure::read('IdeHelper.autoCollectBlacklist');\n\t\tforeach ($blacklist as $value) {\n\t\t\tif (!str_contains($value, '/')) {\n\t\t\t\tunset($variables[$value]);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tforeach ($variables as $name => $variable) {\n\t\t\t\tif (preg_match($value, $name)) {\n\t\t\t\t\tunset($variables[$name]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn $variables;\n\t}\n\n\t/**\n\t * @param array<string, mixed> $variable\n\t *\n\t * @return \\IdeHelper\\Annotation\\AbstractAnnotation\n\t */\n\tprotected function getVariableAnnotation(array $variable) {\n\t\t$defaultType = Configure::read('IdeHelper.autoCollect');\n\t\t$type = $variable['type'];\n\t\tif ($type === null) {\n\t\t\t$type = $defaultType;\n\t\t}\n\n\t\tif (is_callable($defaultType)) {\n\t\t\t$guessedType = $defaultType($variable);\n\t\t\tif ($guessedType) {\n\t\t\t\t$type = $guessedType;\n\t\t\t}\n\t\t}\n\t\tif (!$type || $type === true) {\n\t\t\t$type = 'mixed';\n\t\t}\n\n\t\t/** @var \\IdeHelper\\Annotation\\VariableAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createOrFail(VariableAnnotation::TAG, $type, '$' . $variable['name']);\n\t\t$annotation->setGuessed(true);\n\n\t\t/** @return \\IdeHelper\\Annotator\\AbstractAnnotator */\n\t\treturn $annotation;\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tprotected function isV4(): bool {\n\t\tstatic $isV4 = null;\n\n\t\tif ($isV4 !== null) {\n\t\t\treturn $isV4;\n\t\t}\n\n\t\t// PHPCS v4+ requires blank line after <?php for PSR12 compliance\n\t\t// Check version from Config class constant\n\t\tif (class_exists(Config::class)) {\n\t\t\t$version = Config::VERSION;\n\t\t\t$isV4 = version_compare($version, '4.0.0', '>=');\n\t\t} else {\n\t\t\t// Fallback: assume v4+ if class doesn't exist\n\t\t\t$isV4 = true;\n\t\t}\n\n\t\treturn $isV4;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int|null $phpOpenTagIndex\n\t *\n\t * @return int|null\n\t */\n\tprotected function checkForDeclareStatement(File $file, ?int $phpOpenTagIndex): ?int {\n\t\tif ($phpOpenTagIndex === null) {\n\t\t\treturn $phpOpenTagIndex;\n\t\t}\n\n\t\t$tokens = $file->getTokens();\n\n\t\t// Check if there's a declare statement right after the php open tag (on same or next line)\n\t\t$nextNonWhitespace = $file->findNext(T_WHITESPACE, $phpOpenTagIndex + 1, null, true);\n\t\tif ($nextNonWhitespace === false || $tokens[$nextNonWhitespace]['code'] !== T_DECLARE) {\n\t\t\treturn $phpOpenTagIndex;\n\t\t}\n\n\t\t// Check if declare is on the same line or immediately after php tag\n\t\tif ($tokens[$nextNonWhitespace]['line'] > $tokens[$phpOpenTagIndex]['line'] + 1) {\n\t\t\treturn $phpOpenTagIndex;\n\t\t}\n\n\t\t$lastIndexOfRow = $tokens[$nextNonWhitespace]['parenthesis_closer'];\n\t\twhile (!empty($tokens[$lastIndexOfRow + 1]) && $tokens[$lastIndexOfRow + 1]['line'] === $tokens[$lastIndexOfRow]['line']) {\n\t\t\t$lastIndexOfRow++;\n\t\t}\n\n\t\treturn $lastIndexOfRow;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/Traits/ComponentTrait.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\Traits;\n\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\Plugin;\n\n/**\n * Handles component related things\n */\ntrait ComponentTrait {\n\n\t/**\n\t * @param string $component\n\t * @param bool $includeApp\n\t *\n\t * @return string|null\n\t */\n\tprotected function findClassName(string $component, bool $includeApp): ?string {\n\t\t$plugins = Plugin::all();\n\t\tif (class_exists($component)) {\n\t\t\treturn $component;\n\t\t}\n\n\t\t$className = App::className($component, 'Controller/Component', 'Component', $includeApp);\n\t\tif ($className) {\n\t\t\treturn $className;\n\t\t}\n\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$className = App::className($plugin . '.' . $component, 'Controller/Component', 'Component');\n\t\t\tif ($className) {\n\t\t\t\treturn $className;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/Traits/DocBlockTrait.php",
    "content": "<?php\n\n/**\n * MIT License\n * For full license information, please view the LICENSE file that was distributed with this source code.\n */\n\nnamespace IdeHelper\\Annotator\\Traits;\n\nuse PHP_CodeSniffer\\Files\\File;\nuse PHPStan\\PhpDocParser\\Ast\\PhpDoc\\GenericTagValueNode;\nuse PHPStan\\PhpDocParser\\Ast\\PhpDoc\\MethodTagValueNode;\nuse PHPStan\\PhpDocParser\\Ast\\PhpDoc\\ParamTagValueNode;\nuse PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagValueNode;\nuse PHPStan\\PhpDocParser\\Ast\\PhpDoc\\ReturnTagValueNode;\nuse PHPStan\\PhpDocParser\\Ast\\PhpDoc\\ThrowsTagValueNode;\nuse PHPStan\\PhpDocParser\\Ast\\PhpDoc\\VarTagValueNode;\nuse PHPStan\\PhpDocParser\\Ast\\Type\\UnionTypeNode;\nuse PHPStan\\PhpDocParser\\Lexer\\Lexer;\nuse PHPStan\\PhpDocParser\\Parser\\ConstExprParser;\nuse PHPStan\\PhpDocParser\\Parser\\PhpDocParser;\nuse PHPStan\\PhpDocParser\\Parser\\TokenIterator;\nuse PHPStan\\PhpDocParser\\Parser\\TypeParser;\nuse PHPStan\\PhpDocParser\\ParserConfig;\n\n/**\n * Common functionality around doc block parsing and writing.\n */\ntrait DocBlockTrait {\n\n\t/**\n\t * @param string $tagName tag name\n\t * @param string $tagComment tag comment\n\t *\n\t * @return \\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagValueNode\n\t */\n\tprotected static function getValueNode(string $tagName, string $tagComment): PhpDocTagValueNode {\n\t\tstatic $phpDocParser;\n\t\tif (!$phpDocParser) {\n\t\t\t$config = new ParserConfig(usedAttributes: []);\n\t\t\t$constExprParser = new ConstExprParser($config);\n\t\t\t$phpDocParser = new PhpDocParser($config, new TypeParser($config, $constExprParser), $constExprParser);\n\t\t}\n\n\t\tstatic $phpDocLexer;\n\t\tif (!$phpDocLexer) {\n\t\t\t$config = new ParserConfig(usedAttributes: []);\n\t\t\t$phpDocLexer = new Lexer($config);\n\t\t}\n\n\t\treturn $phpDocParser->parseTagValue(new TokenIterator($phpDocLexer->tokenize($tagComment)), $tagName);\n\t}\n\n\t/**\n\t * @param \\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\MethodTagValueNode|\\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PropertyTagValueNode|\\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\VarTagValueNode|\\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\ParamTagValueNode|\\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\ReturnTagValueNode|\\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\GenericTagValueNode $valueNode\n\t *\n\t * @return array<string>\n\t */\n\tprotected function valueNodeParts(PhpDocTagValueNode $valueNode): array {\n\t\tif ($valueNode instanceof MethodTagValueNode) {\n\t\t\t$types = [$valueNode->returnType];\n\t\t} elseif ($valueNode instanceof GenericTagValueNode) {\n\t\t\t$types = [$valueNode];\n\t\t} elseif ($valueNode->type instanceof UnionTypeNode) {\n\t\t\t$types = $valueNode->type->types;\n\t\t} else {\n\t\t\t$types = [$valueNode->type];\n\t\t}\n\n\t\t$result = [];\n\t\tforeach ($types as $type) {\n\t\t\t$result[] = (string)$type;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param array<string> $parts\n\t * @param \\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagValueNode $valueNode\n\t *\n\t * @return string\n\t */\n\tprotected function stringifyValueNode(array $parts, PhpDocTagValueNode $valueNode): string {\n\t\tif ($valueNode instanceof ParamTagValueNode) {\n\t\t\treturn trim(sprintf(\n\t\t\t\t'%s %s%s %s',\n\t\t\t\timplode('|', $parts),\n\t\t\t\t$valueNode->isVariadic ? '...' : '',\n\t\t\t\t$valueNode->parameterName,\n\t\t\t\t$valueNode->description,\n\t\t\t));\n\t\t}\n\t\tif ($valueNode instanceof ReturnTagValueNode) {\n\t\t\treturn trim(sprintf(\n\t\t\t\t'%s%s',\n\t\t\t\timplode('|', $parts),\n\t\t\t\t$valueNode->description,\n\t\t\t));\n\t\t}\n\t\tif ($valueNode instanceof MethodTagValueNode) {\n\t\t\treturn trim(sprintf(\n\t\t\t\t'%s %s() %s',\n\t\t\t\timplode('|', $parts),\n\t\t\t\t$valueNode->methodName,\n\t\t\t\t$valueNode->description,\n\t\t\t));\n\t\t}\n\t\tif ($valueNode instanceof VarTagValueNode) {\n\t\t\treturn trim(sprintf(\n\t\t\t\t'%s %s%s',\n\t\t\t\timplode('|', $parts),\n\t\t\t\t$valueNode->variableName,\n\t\t\t\t$valueNode->description,\n\t\t\t));\n\t\t}\n\t\tif ($valueNode instanceof ThrowsTagValueNode) {\n\t\t\treturn trim(sprintf(\n\t\t\t\t'%s %s',\n\t\t\t\timplode('|', $parts),\n\t\t\t\t$valueNode->description,\n\t\t\t));\n\t\t}\n\n\t\treturn trim(implode('|', $parts));\n\t}\n\n\t/**\n\t * Looks for either `@inheritDoc` or `{@inheritDoc}`.\n\t * Also allows `@inheritdoc` or `{@inheritdoc}` aliases.\n\t *\n\t * @param \\PHP_CodeSniffer\\Files\\File $phpCsFile\n\t * @param int $docBlockStartIndex\n\t * @param int $docBlockEndIndex\n\t * @param string $alias\n\t *\n\t * @return bool\n\t */\n\tprotected function hasInheritDoc(File $phpCsFile, int $docBlockStartIndex, int $docBlockEndIndex, string $alias = '@inheritDoc'): bool {\n\t\t$tokens = $phpCsFile->getTokens();\n\n\t\tfor ($i = $docBlockStartIndex + 1; $i < $docBlockEndIndex; ++$i) {\n\t\t\tif (empty($tokens[$i]['content'])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$content = $tokens[$i]['content'];\n\t\t\t$pos = stripos($content, $alias);\n\t\t\tif ($pos === false) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($pos && str_starts_with($alias, '@') && substr($content, $pos - 1, $pos) === '{') {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Checks for ...<...>.\n\t *\n\t * @param array<string> $docBlockTypes\n\t *\n\t * @return bool\n\t */\n\tprotected function containsIterableSyntax(array $docBlockTypes): bool {\n\t\tforeach ($docBlockTypes as $docBlockType) {\n\t\t\tif (str_contains($docBlockType, '<')) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param array<\\PHPStan\\PhpDocParser\\Ast\\Type\\TypeNode|string> $typeNodes type nodes\n\t *\n\t * @return string\n\t */\n\tprotected function renderUnionTypes(array $typeNodes): string {\n\t\t$string = (string)preg_replace(\n\t\t\t[\n\t\t\t\t'/ ([|&]) /', // Spaces around union types (int | string) -> (int|string)\n\t\t\t\t'/\\(<([^()]+)>\\)/', // Remove unnecessary parentheses around union types (int|string)\n\t\t\t\t'/<\\(/', // Replaces `(<` with `<`\n\t\t\t\t'/\\)>/', // Replaces `)>` with `>`\n\t\t\t\t'/\\)}/', // Replaces `)}` with `}`\n\t\t\t\t'/\\),/', // Replaces `), ` with `,`\n\t\t\t\t'/[:,] \\(/', // Replaces `: (` with `: ` and `, (` with `, `\n\t\t\t],\n\t\t\t[\n\t\t\t\t'${1}',\n\t\t\t\t'${1}',\n\t\t\t\t'<',\n\t\t\t\t'>',\n\t\t\t\t'}',\n\t\t\t\t',',\n\t\t\t\t', ',\n\t\t\t],\n\t\t\timplode('|', $typeNodes),\n\t\t);\n\n\t\tif (str_starts_with($string, '(') && str_ends_with($string, ')')) {\n\t\t\t$string = substr($string, 1, -1);\n\t\t}\n\n\t\treturn $string;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/Traits/FileTrait.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\Traits;\n\nuse PHP_CodeSniffer\\Config;\nuse PHP_CodeSniffer\\Files\\File;\nuse PHP_CodeSniffer\\Fixer;\nuse PHP_CodeSniffer\\Ruleset;\nuse PHP_CodeSniffer\\Runner;\n\ntrait FileTrait {\n\n\t/**\n\t * @param string $file\n\t * @param string|null $content\n\t *\n\t * @return \\PHP_CodeSniffer\\Files\\File\n\t */\n\tprotected function getFile(string $file, ?string $content = null): File {\n\t\t$_SERVER['argv'] = [];\n\n\t\t$phpcs = new Runner();\n\n\t\tif (!defined('PHP_CODESNIFFER_CBF')) {\n\t\t\tdefine('PHP_CODESNIFFER_CBF', false);\n\t\t}\n\t\t// Explictly specifying standard prevents it from searching for phpcs.xml type files.\n\t\t$config = new Config(['--standard=PSR2']);\n\t\t$phpcs->config = $config;\n\t\t$phpcs->init();\n\n\t\t$ruleset = new Ruleset($config);\n\n\t\t$fileObject = new File($file, $ruleset, $config);\n\t\t$fileObject->setContent($content ?? (string)file_get_contents($file));\n\t\t$fileObject->parse();\n\n\t\treturn $fileObject;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t *\n\t * @return \\PHP_CodeSniffer\\Fixer\n\t */\n\tprotected function getFixer(File $file): Fixer {\n\t\t$fixer = new Fixer();\n\n\t\t$fixer->startFile($file);\n\n\t\treturn $fixer;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/Traits/HelperTrait.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\Traits;\n\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\Plugin;\n\n/**\n * Handles component related things\n */\ntrait HelperTrait {\n\n\t/**\n\t * @param string $helper\n\t * @param bool $includeApp\n\t *\n\t * @return string|null\n\t */\n\tprotected function findClassName(string $helper, bool $includeApp): ?string {\n\t\t$className = App::className($helper, 'View/Helper', 'Helper', $includeApp);\n\t\tif ($className) {\n\t\t\treturn $className;\n\t\t}\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$className = App::className($plugin . '.' . $helper, 'View/Helper', 'Helper');\n\t\t\tif ($className) {\n\t\t\t\treturn $className;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/Traits/ModelTrait.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator\\Traits;\n\n/**\n * Handles model related things\n */\ntrait ModelTrait {\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return array<string>\n\t */\n\tprotected function getUsedModels(string $content): array {\n\t\tpreg_match_all('/\\$this->([a-z]+)\\s*=\\s*\\$this->fetchTable\\(\\'([a-z.]+)\\'/i', $content, $matches);\n\t\tif (empty($matches[1])) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$properties = $matches[1];\n\t\t$tables = $matches[2];\n\t\t$models = array_combine($properties, $tables);\n\n\t\tpreg_match_all('/\\b(public|protected|private) \\$([a-z]+)\\b/i', $content, $propertyMatches);\n\t\t$excluded = $propertyMatches[2];\n\t\tforeach ($excluded as $property) {\n\t\t\tif (isset($models[$property])) {\n\t\t\t\tunset($models[$property]);\n\t\t\t}\n\t\t}\n\n\t\treturn $models;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/Traits/UseStatementsTrait.php",
    "content": "<?php\n\n/**\n * MIT License\n * For full license information, please view the LICENSE file that was distributed with this source code.\n */\n\nnamespace IdeHelper\\Annotator\\Traits;\n\nuse PHP_CodeSniffer\\Files\\File;\nuse PHP_CodeSniffer\\Util\\Tokens;\n\ntrait UseStatementsTrait {\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $phpcsFile\n\t *\n\t * @return array<string, array<string, mixed>>\n\t */\n\tprotected function getUseStatements(File $phpcsFile): array {\n\t\t$tokens = $phpcsFile->getTokens();\n\n\t\t$statements = [];\n\t\tforeach ($tokens as $index => $token) {\n\t\t\tif ($token['code'] !== T_USE || $token['level'] > 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$useStatementStartIndex = $phpcsFile->findNext(Tokens::$emptyTokens, $index + 1, null, true);\n\n\t\t\t// Ignore function () use ($foo) {}\n\t\t\tif (!$useStatementStartIndex || $tokens[$useStatementStartIndex]['content'] === '(') {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$semicolonIndex = $phpcsFile->findNext(T_SEMICOLON, $useStatementStartIndex + 1);\n\t\t\t$useStatementEndIndex = $phpcsFile->findPrevious(Tokens::$emptyTokens, $semicolonIndex - 1, null, true);\n\t\t\tif (!$useStatementEndIndex) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$statement = '';\n\t\t\tfor ($i = $useStatementStartIndex; $i <= $useStatementEndIndex; $i++) {\n\t\t\t\t$statement .= $tokens[$i]['content'];\n\t\t\t}\n\n\t\t\t// Another sniff takes care of that, we just ignore then.\n\t\t\tif ($this->isMultipleUseStatement($statement)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$statementParts = preg_split('/\\s+as\\s+/i', $statement) ?: [];\n\n\t\t\tif (count($statementParts) === 1) {\n\t\t\t\t$fullName = $statement;\n\t\t\t\t$statementParts = explode('\\\\', $fullName);\n\t\t\t\t$shortName = end($statementParts);\n\t\t\t\t$alias = null;\n\t\t\t} else {\n\t\t\t\t$fullName = $statementParts[0];\n\t\t\t\t$alias = $statementParts[1];\n\t\t\t\t$statementParts = explode('\\\\', $fullName);\n\t\t\t\t$shortName = end($statementParts);\n\t\t\t}\n\n\t\t\t$shortName = trim($shortName);\n\t\t\t$fullName = trim($fullName);\n\t\t\t$key = $alias ?: $shortName;\n\n\t\t\t$statements[$key] = [\n\t\t\t\t'alias' => $alias,\n\t\t\t\t'end' => $semicolonIndex,\n\t\t\t\t'statement' => $statement,\n\t\t\t\t'fullName' => ltrim($fullName, '\\\\'),\n\t\t\t\t'shortName' => $shortName,\n\t\t\t\t'start' => $index,\n\t\t\t];\n\t\t}\n\n\t\treturn $statements;\n\t}\n\n\t/**\n\t * @param string $statementContent\n\t *\n\t * @return bool\n\t */\n\tprotected function isMultipleUseStatement(string $statementContent): bool {\n\t\tif (str_contains($statementContent, ',')) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "src/Annotator/ViewAnnotator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Annotator;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Http\\ServerRequest;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse IdeHelper\\Annotator\\Traits\\HelperTrait;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\nuse RuntimeException;\n\nclass ViewAnnotator extends AbstractAnnotator {\n\n\tuse HelperTrait;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $helpers = [];\n\n\t/**\n\t * @param string $path Path to file.\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\n\t\t$helpers = $this->getHelpers();\n\t\t$annotations = $this->buildAnnotations($helpers);\n\n\t\treturn $this->annotateContent($path, $content, $annotations);\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function getHelpers(): array {\n\t\t$helperArray = $this->parseViewClass();\n\n\t\t$helperArray = $this->addAppHelpers($helperArray);\n\t\t$helperArray = $this->addExtractedHelpers($helperArray);\n\n\t\treturn $helperArray;\n\t}\n\n\t/**\n\t * @param array<string, string> $helperArray\n\t * @return array<string, string>\n\t */\n\tprotected function addExtractedHelpers(array $helperArray) {\n\t\t$folders = $this->getFolders();\n\n\t\t$this->helpers = [];\n\t\tforeach ($folders as $folder) {\n\t\t\t$this->checkTemplates($folder);\n\t\t}\n\n\t\t$helpers = array_unique($this->helpers);\n\n\t\tforeach ($helpers as $helper) {\n\t\t\tif (isset($helperArray[$helper])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$className = $this->findClassName($helper, !$this->getConfig(static::CONFIG_PLUGIN));\n\t\t\tif (!$className || str_starts_with($className, 'Cake\\\\')) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$helperArray[$helper] = $className;\n\t\t}\n\n\t\treturn $helperArray;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @return void\n\t */\n\tprotected function checkTemplates(string $folder) {\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, false, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\t$content = file_get_contents($file);\n\t\t\tif ($content === false) {\n\t\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t\t}\n\t\t\t$helpers = $this->parseHelpersInContent($content);\n\t\t\t$this->helpers = array_merge($this->helpers, $helpers);\n\t\t}\n\n\t\tforeach ($folderContent[0] as $subFolder) {\n\t\t\t$this->checkTemplates($subFolder);\n\t\t}\n\t}\n\n\t/**\n\t * @param string $content\n\t *\n\t * @return array<string>\n\t */\n\tprotected function parseHelpersInContent(string $content) {\n\t\tpreg_match_all('/\\$this->([A-Z][A-Za-z]+)->/', $content, $matches);\n\t\tif (empty($matches[1])) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$helpers = array_unique($matches[1]);\n\n\t\treturn $helpers;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function parseViewClass(): array {\n\t\t$helpers = [];\n\n\t\t/** @phpstan-var class-string<object> $className */\n\t\t$className = App::classNameOrFail('App', 'Controller', 'Controller');\n\t\tif ($this->_isAbstract($className)) {\n\t\t\treturn [];\n\t\t}\n\n\t\t/** @var \\Cake\\Controller\\Controller $Controller */\n\t\t$Controller = new $className(new ServerRequest());\n\t\t$View = $Controller->createView();\n\n\t\tforeach ($View->helpers() as $alias => $helper) {\n\t\t\t$className = get_class($helper);\n\t\t\tif (str_starts_with($className, 'Cake\\\\')) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$helpers[$alias] = $className;\n\t\t}\n\n\t\treturn $helpers;\n\t}\n\n\t/**\n\t * @param array<string> $helperArray\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addAppHelpers(array $helperArray): array {\n\t\t$paths = AppPath::get('View/Helper');\n\t\tforeach ($paths as $path) {\n\t\t\t$folderContent = (new Folder($path))->read(Folder::SORT_NAME, true);\n\t\t\tif (empty($folderContent[1])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$helpers = $folderContent[1];\n\t\t\tforeach ($helpers as $helper) {\n\t\t\t\tif (!preg_match('/^(.+)Helper.php$/', $helper, $matches)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$helper = $matches[1];\n\t\t\t\tif (isset($helperArray[$helper])) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$helperArray[$helper] = Configure::read('App.namespace') . '\\\\View\\\\Helper\\\\' . $helper . 'Helper';\n\t\t\t}\n\t\t}\n\n\t\treturn $helperArray;\n\t}\n\n\t/**\n\t * @param array<string> $helpers\n\t *\n\t * @return array<\\IdeHelper\\Annotation\\AbstractAnnotation>\n\t */\n\tprotected function buildAnnotations(array $helpers): array {\n\t\t$annotations = [];\n\t\tforeach ($helpers as $alias => $className) {\n\t\t\t$annotations[] = AnnotationFactory::createOrFail(PropertyAnnotation::TAG, '\\\\' . $className, '$' . $alias);\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function getFolders(): array {\n\t\t$plugin = null;\n\t\t$folders = App::path('templates', $plugin);\n\t\t$plugins = Configure::read('IdeHelper.includedPlugins');\n\t\tif ($plugins === true) {\n\t\t\t$plugins = Plugin::loaded(); // We cannot use all() here\n\t\t} else {\n\t\t\t$plugins = (array)$plugins;\n\t\t}\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$folders = array_merge($folders, App::path('templates', $plugin));\n\t\t}\n\n\t\treturn $folders;\n\t}\n\n}\n"
  },
  {
    "path": "src/CodeCompletion/CodeCompletionGenerator.php",
    "content": "<?php\n\nnamespace IdeHelper\\CodeCompletion;\n\nuse Cake\\Core\\Configure;\nuse RuntimeException;\n\nclass CodeCompletionGenerator {\n\n\tprotected TaskCollection $taskCollection;\n\n\t/**\n\t * @param \\IdeHelper\\CodeCompletion\\TaskCollection $taskCollection\n\t */\n\tpublic function __construct(TaskCollection $taskCollection) {\n\t\t$this->taskCollection = $taskCollection;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tpublic function generate(): array {\n\t\t$map = $this->taskCollection->getMap();\n\n\t\tforeach ($map as $namespace => $array) {\n\t\t\t$content = $this->buildContent($array);\n\n\t\t\t$template = <<<CODE\n<?php\nnamespace $namespace;\n\n/**\n * Only for code completion - regenerate using `bin/cake code_completion generate`.\n */\n$content\nCODE;\n\n\t\t\t$path = $this->path();\n\t\t\t$filename = $path . 'CodeCompletion' . $this->type($namespace) . '.php';\n\n\t\t\tif (!file_exists($filename) || md5_file($filename) !== md5($template)) {\n\t\t\t\tif (file_put_contents($filename, $template) === false) {\n\t\t\t\t\tthrow new RuntimeException(sprintf('Failed to write file `%s`.', $filename));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn array_keys($map);\n\t}\n\n\t/**\n\t * @param array<string> $array\n\t *\n\t * @return string\n\t */\n\tprotected function buildContent(array $array): string {\n\t\treturn implode('', $array);\n\t}\n\n\t/**\n\t * @param string $namespace\n\t *\n\t * @return string\n\t */\n\tprotected function type(string $namespace): string {\n\t\treturn (string)preg_replace('/[^\\da-z]/i', '', $namespace);\n\t}\n\n\t/**\n\t * @throws \\RuntimeException When the directory cannot be created.\n\t * @return string\n\t */\n\tprotected function path(): string {\n\t\t$path = Configure::read('IdeHelper.codeCompletionPath') ?: TMP;\n\t\tif (!is_dir($path) && !mkdir($path, 0770, true) && !is_dir($path)) {\n\t\t\tthrow new RuntimeException(sprintf('Cannot create directory `%s`.', $path));\n\t\t}\n\n\t\treturn $path;\n\t}\n\n}\n"
  },
  {
    "path": "src/CodeCompletion/Task/BehaviorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\CodeCompletion\\Task;\n\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\n\nclass BehaviorTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TYPE_NAMESPACE = 'Cake\\ORM';\n\n\t/**\n\t * @return string\n\t */\n\tpublic function type(): string {\n\t\treturn static::TYPE_NAMESPACE;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function create(): string {\n\t\t$behaviors = $this->collectBehaviors();\n\t\tif (!$behaviors) {\n\t\t\treturn '';\n\t\t}\n\n\t\t$content = $this->build($behaviors);\n\n\t\t$content = <<<CODE\nabstract class BehaviorRegistry extends \\Cake\\Core\\ObjectRegistry {\n\n$content\n\n}\n\nCODE;\n\n\t\treturn $content;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectBehaviors(): array {\n\t\t$behaviors = [];\n\n\t\t$folders = array_merge(App::core('ORM/Behavior'), AppPath::get('Model/Behavior'));\n\t\tforeach ($folders as $folder) {\n\t\t\t$behaviors = $this->addBehaviors($behaviors, $folder);\n\t\t}\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$folders = AppPath::get('Model/Behavior', $plugin);\n\t\t\tforeach ($folders as $folder) {\n\t\t\t\t$behaviors = $this->addBehaviors($behaviors, $folder, $plugin);\n\t\t\t}\n\t\t}\n\n\t\treturn $behaviors;\n\t}\n\n\t/**\n\t * @param array<string> $behaviors\n\t * @param string $folder\n\t * @param string|null $plugin\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addBehaviors(array $behaviors, string $folder, ?string $plugin = null): array {\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\tpreg_match('/^(.+)Behavior\\.php$/', $file, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$name = $matches[1];\n\t\t\tif ($plugin) {\n\t\t\t\t$name = $plugin . '.' . $name;\n\t\t\t}\n\n\t\t\t$className = App::className($name, 'Model/Behavior', 'Behavior');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$behaviors[$name] = $className;\n\t\t}\n\n\t\treturn $behaviors;\n\t}\n\n\t/**\n\t * @param array<string> $behaviors\n\t *\n\t * @return string\n\t */\n\tprotected function build(array $behaviors): string {\n\t\t$result = [];\n\n\t\tforeach ($behaviors as $behavior => $className) {\n\t\t\t[$plugin, $name] = pluginSplit($behavior);\n\n\t\t\t$template = <<<CODE\n\t/**\n\t * $behavior behavior.\n\t *\n\t * @var \\\\$className\n\t */\n\tpublic \\$$name;\nCODE;\n\t\t\t$result[] = $template;\n\t\t}\n\n\t\treturn implode(PHP_EOL . PHP_EOL, $result);\n\t}\n\n}\n"
  },
  {
    "path": "src/CodeCompletion/Task/ControllerEventsTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\CodeCompletion\\Task;\n\nuse Cake\\Core\\Configure;\n\nclass ControllerEventsTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TYPE_NAMESPACE = 'Cake\\Controller';\n\n\t/**\n\t * @return string\n\t */\n\tpublic function type(): string {\n\t\treturn static::TYPE_NAMESPACE;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function create(): string {\n\t\t/** @var bool|null $returnType */\n\t\t$returnType = Configure::read('IdeHelper.codeCompletionReturnType');\n\n\t\t$controllerEvents = $this->events($returnType ?? false);\n\t\t$componentEvents = $this->events($returnType ?? true);\n\n\t\treturn <<<CODE\n\nuse Cake\\Event\\EventInterface;\nuse Cake\\Http\\Response;\nuse Psr\\Http\\Message\\UriInterface;\n\nif (false) {\n\tclass Controller {\n$controllerEvents\n\t}\n\n\tclass Component {\n$componentEvents\n\t}\n}\n\nCODE;\n\t}\n\n\t/**\n\t * @param bool $returnType\n\t *\n\t * @return string\n\t */\n\tprotected function events(bool $returnType): string {\n\t\t$type = null;\n\t\t$docBlock = null;\n\t\tif ($returnType) {\n\t\t\t$type = ': ' . 'void';\n\t\t} else {\n\t\t\t$docBlock = <<<TXT\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface \\$event\n\t\t *\n\t\t * @return void\n\t\t */\nTXT;\n\t\t\t$docBlock = trim($docBlock) . PHP_EOL . str_repeat(\"\\t\", 2);\n\t\t}\n\t\t$docBlockRedirect = <<<TXT\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface \\$event\n\t\t * @param \\Psr\\Http\\Message\\UriInterface|array|string \\$url\n\t\t * @param \\Cake\\Http\\Response \\$response\n\t\t *\n\t\t * @return void\n\t\t */\nTXT;\n\t\t$docBlockRedirect = trim($docBlockRedirect) . PHP_EOL . str_repeat(\"\\t\", 2);\n\n\t\t$events = <<<TXT\n\t\t{$docBlock}public function startup(EventInterface \\$event)$type {\n\t\t}\n\n\t\t{$docBlock}public function beforeFilter(EventInterface \\$event)$type {\n\t\t}\n\n\t\t{$docBlock}public function beforeRender(EventInterface \\$event)$type {\n\t\t}\n\n\t\t{$docBlock}public function afterFilter(EventInterface \\$event)$type {\n\t\t}\n\n\t\t{$docBlock}public function shutdown(EventInterface \\$event)$type {\n\t\t}\n\n\t\t{$docBlockRedirect}public function beforeRedirect(EventInterface \\$event, UriInterface|array|string \\$url, Response \\$response)$type {\n\t\t}\nTXT;\n\n\t\treturn $events;\n\t}\n\n}\n"
  },
  {
    "path": "src/CodeCompletion/Task/ModelEventsTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\CodeCompletion\\Task;\n\nclass ModelEventsTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TYPE_NAMESPACE = 'Cake\\ORM';\n\n\t/**\n\t * @return string\n\t */\n\tpublic function type(): string {\n\t\treturn static::TYPE_NAMESPACE;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function create(): string {\n\t\t$events = <<<'TXT'\n\t\tpublic function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void {}\n\t\tpublic function afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, bool $primary): void {}\n\t\tpublic function buildValidator(EventInterface $event, Validator $validator, string $name): void {}\n\t\tpublic function buildRules(RulesChecker $rules): RulesChecker { return $rules; }\n\t\tpublic function beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation): void {}\n\t\tpublic function afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation): void {}\n\t\tpublic function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\nTXT;\n\n\t\treturn <<<CODE\n\nuse ArrayObject;\nuse Cake\\Datasource\\EntityInterface;\nuse Cake\\Event\\EventInterface;\nuse Cake\\ORM\\Query\\SelectQuery;\nuse Cake\\ORM\\RulesChecker;\nuse Cake\\Validation\\Validator;\n\nif (false) {\n\tclass Table {\n$events\n\t}\n\n\tclass Behavior {\n$events\n\t}\n}\n\nCODE;\n\t}\n\n}\n"
  },
  {
    "path": "src/CodeCompletion/Task/SelectQueryTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\CodeCompletion\\Task;\n\nclass SelectQueryTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TYPE_NAMESPACE = 'Cake\\ORM\\Query';\n\n\t/**\n\t * @return string\n\t */\n\tpublic function type(): string {\n\t\treturn static::TYPE_NAMESPACE;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function create(): string {\n\t\treturn <<<'CODE'\nuse Cake\\Database\\ExpressionInterface;\nuse Cake\\Datasource\\ResultSetInterface;\nuse Closure;\nuse Psr\\SimpleCache\\CacheInterface;\n\nif (false) {\n\t/**\n\t * @template TSubject\n\t */\n\tclass SelectQuery {\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function find(string $finder, mixed ...$args) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function where(\n\t\t\tExpressionInterface|Closure|array|string|null $conditions = null,\n\t\t\tarray $types = [],\n\t\t\tbool $overwrite = false,\n\t\t) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function andWhere($conditions, array $types = []) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function matching(string $assoc, ?Closure $builder = null) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function leftJoinWith(string $assoc, ?Closure $builder = null) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function innerJoinWith(string $assoc, ?Closure $builder = null) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function notMatching(string $assoc, ?Closure $builder = null) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function contain(mixed $associations, Closure|bool $override = false) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function clearContain() {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function cache(Closure|string|false $key, CacheInterface|string $config = 'default') {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function groupBy(ExpressionInterface|array|string $fields, bool $overwrite = false) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function orderBy(ExpressionInterface|Closure|array|string $fields, bool $overwrite = false) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function enableAutoFields(bool $value = true) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function disableAutoFields() {}\n\n\t\t/**\n\t\t * @return static<array<string,mixed>>\n\t\t */\n\t\tpublic function disableHydration() {}\n\n\t\t/**\n\t\t * @return ResultSetInterface<array-key, TSubject>\n\t\t */\n\t\tpublic function all() {}\n\n\t\t/**\n\t\t * @return TSubject|null\n\t\t */\n\t\tpublic function first() {}\n\n\t\t/**\n\t\t * @return TSubject\n\t\t */\n\t\tpublic function firstOrFail() {}\n\n\t\t/**\n\t\t * @return array<TSubject>\n\t\t */\n\t\tpublic function toArray() {}\n\t}\n}\n\nCODE;\n\t}\n\n}\n"
  },
  {
    "path": "src/CodeCompletion/Task/TaskInterface.php",
    "content": "<?php\n\nnamespace IdeHelper\\CodeCompletion\\Task;\n\ninterface TaskInterface {\n\n\t/**\n\t * @return string\n\t */\n\tpublic function type(): string;\n\n\t/**\n\t * @return string\n\t */\n\tpublic function create(): string;\n\n}\n"
  },
  {
    "path": "src/CodeCompletion/Task/ViewEventsTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\CodeCompletion\\Task;\n\nclass ViewEventsTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const TYPE_NAMESPACE = 'Cake\\View';\n\n\t/**\n\t * @return string\n\t */\n\tpublic function type(): string {\n\t\treturn static::TYPE_NAMESPACE;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function create(): string {\n\t\t$events = <<<'TXT'\n\t\tpublic function beforeRenderFile(EventInterface $event): void {}\n\t\tpublic function afterRenderFile(EventInterface $event): void {}\n\t\tpublic function beforeRender(EventInterface $event): void {}\n\t\tpublic function afterRender(EventInterface $event): void {}\n\t\tpublic function beforeLayout(EventInterface $event): void {}\n\t\tpublic function afterLayout(EventInterface $event): void {}\nTXT;\n\n\t\treturn <<<CODE\n\nuse Cake\\Event\\EventInterface;\n\nif (false) {\n\tclass Helper {\n$events\n\t}\n}\n\nCODE;\n\t}\n\n}\n"
  },
  {
    "path": "src/CodeCompletion/TaskCollection.php",
    "content": "<?php\n\nnamespace IdeHelper\\CodeCompletion;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\CodeCompletion\\Task\\BehaviorTask;\nuse IdeHelper\\CodeCompletion\\Task\\ControllerEventsTask;\nuse IdeHelper\\CodeCompletion\\Task\\ModelEventsTask;\nuse IdeHelper\\CodeCompletion\\Task\\SelectQueryTask;\nuse IdeHelper\\CodeCompletion\\Task\\TaskInterface;\nuse IdeHelper\\CodeCompletion\\Task\\ViewEventsTask;\nuse InvalidArgumentException;\n\nclass TaskCollection {\n\n\t/**\n\t * @var array<class-string<\\IdeHelper\\CodeCompletion\\Task\\TaskInterface>, class-string<\\IdeHelper\\CodeCompletion\\Task\\TaskInterface>>\n\t */\n\tprotected array $defaultTasks = [\n\t\tBehaviorTask::class => BehaviorTask::class,\n\t\tModelEventsTask::class => ModelEventsTask::class,\n\t\tSelectQueryTask::class => SelectQueryTask::class,\n\t\tControllerEventsTask::class => ControllerEventsTask::class,\n\t\tViewEventsTask::class => ViewEventsTask::class,\n\t];\n\n\t/**\n\t * @var array<\\IdeHelper\\CodeCompletion\\Task\\TaskInterface>\n\t */\n\tprotected array $tasks = [];\n\n\t/**\n\t * @param array<class-string<\\IdeHelper\\CodeCompletion\\Task\\TaskInterface>|\\IdeHelper\\Generator\\Task\\TaskInterface> $tasks\n\t */\n\tpublic function __construct(array $tasks = []) {\n\t\t$defaultTasks = (array)Configure::read('IdeHelper.codeCompletionTasks') + $this->defaultTasks;\n\t\t$tasks += $defaultTasks;\n\n\t\tforeach ($tasks as $task) {\n\t\t\tif (!$task) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$this->add($task);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a task to the collection.\n\t *\n\t * @param \\IdeHelper\\CodeCompletion\\Task\\TaskInterface|class-string<\\IdeHelper\\CodeCompletion\\Task\\TaskInterface> $task The task to map.\n\t * @throws \\InvalidArgumentException\n\t * @return $this\n\t */\n\tprotected function add($task) {\n\t\tif (is_string($task)) {\n\t\t\t$task = new $task();\n\t\t}\n\n\t\t$class = get_class($task);\n\t\tif (!$task instanceof TaskInterface) {\n\t\t\tthrow new InvalidArgumentException(\n\t\t\t\tsprintf('Cannot use `%s` as task, it is not implementing `%s`.', $class, TaskInterface::class),\n\t\t\t);\n\t\t}\n\n\t\t$this->tasks[$class] = $task;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array<\\IdeHelper\\CodeCompletion\\Task\\TaskInterface>\n\t */\n\tpublic function tasks(): array {\n\t\treturn $this->tasks;\n\t}\n\n\t/**\n\t * @return array<string, array<string>>\n\t */\n\tpublic function getMap(): array {\n\t\t$map = [];\n\t\tforeach ($this->tasks as $task) {\n\t\t\t$snippet = $task->create();\n\t\t\tif (!$snippet) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$map[$task->type()][] = $snippet;\n\t\t}\n\n\t\tksort($map);\n\n\t\treturn $map;\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/AllCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Core\\App;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass AllCommand extends AnnotateCommand {\n\n\tprotected bool $interactive;\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate all supported classes.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$types = [\n\t\t\tModelsCommand::class,\n\t\t\tControllersCommand::class,\n\t\t\tCommandsCommand::class,\n\t\t\tComponentsCommand::class,\n\t\t\tHelpersCommand::class,\n\t\t\tTemplatesCommand::class,\n\t\t];\n\t\tif (!$args->getOption('plugin') && !$args->getOption('filter')) {\n\t\t\t$types[] = ViewCommand::class;\n\t\t}\n\n\t\tif ($args->getOption('remove')) {\n\t\t\t$io->verbose('Skipping \"classes\" and \"callbacks\" annotations, they do not support removing.');\n\t\t} else {\n\t\t\t$types[] = ClassesCommand::class;\n\t\t\t$types[] = CallbacksCommand::class;\n\t\t}\n\n\t\tif (!$args->getOption('interactive')) {\n\t\t\t$this->interactive = false;\n\t\t}\n\n\t\t$changes = false;\n\t\tforeach ($types as $key => $type) {\n\t\t\tif ($key !== 0) {\n\t\t\t\t$io->out('');\n\t\t\t}\n\t\t\t$shortName = App::shortName($type, 'Command', 'Command');\n\t\t\t$shortName = str_replace('IdeHelper.Annotate/', '', $shortName);\n\t\t\tif (!$this->interactive) {\n\t\t\t\t$io->out('[' . $shortName . ']');\n\t\t\t} else {\n\t\t\t\t$in = $io->askChoice($shortName . '?', ['y', 'n', 'a'], 'y');\n\t\t\t\tif ($in === 'a') {\n\t\t\t\t\t$this->abort();\n\t\t\t\t}\n\t\t\t\tif ($in !== 'y') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$commandInstance = new $type();\n\t\t\t$commandInstance->execute($args, $io);\n\n\t\t\tif ($this->_annotatorMadeChanges()) {\n\t\t\t\t$changes = true;\n\t\t\t}\n\t\t}\n\n\t\tif ($args->getOption('ci') && $changes) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/CallbacksCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Console\\ConsoleOptionParser;\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotator\\CallbackAnnotator;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass CallbacksCommand extends AnnotateCommand {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate callback methods using callback annotation tasks. This task is not part of `all` when `-r` is used.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\ConsoleOptionParser $parser\n\t * @return \\Cake\\Console\\ConsoleOptionParser\n\t */\n\tprotected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser {\n\t\t$parser = parent::buildOptionParser($parser);\n\t\t$parser->removeOption('remove');\n\n\t\treturn $parser;\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$paths = $this->getPaths('classes');\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\t$this->setPlugin($plugin);\n\t\t\tforeach ($pluginPaths as $path) {\n\t\t\t\t$folders = glob($path . '*', GLOB_ONLYDIR) ?: [];\n\t\t\t\tforeach ($folders as $folder) {\n\t\t\t\t\t$this->_callbacks($folder . DS);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ($args->getOption('ci') && $this->_annotatorMadeChanges()) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @return void\n\t */\n\tprotected function _callbacks(string $folder) {\n\t\t$this->io->out(str_replace(ROOT . DS, '', $folder), 1, ConsoleIo::VERBOSE);\n\n\t\t$folderContent = glob($folder . '*') ?: [];\n\t\tforeach ($folderContent as $path) {\n\t\t\tif (is_dir($path)) {\n\t\t\t\t$folderName = pathinfo($path, PATHINFO_BASENAME);\n\t\t\t\t$prefixes = (array)Configure::read('IdeHelper.prefixes') ?: null;\n\n\t\t\t\tif ($prefixes !== null && !in_array($folderName, $prefixes, true)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$this->_callbacks($path . DS);\n\t\t\t} else {\n\t\t\t\t$extension = pathinfo($path, PATHINFO_EXTENSION);\n\t\t\t\tif ($extension !== 'php') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\t\t\tif ($this->_shouldSkip($name, $path)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$this->io->out('-> ' . $name, 1, ConsoleIo::VERBOSE);\n\n\t\t\t\t$annotator = $this->getAnnotator(CallbackAnnotator::class);\n\t\t\t\t$annotator->annotate($path);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/ClassesCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Console\\ConsoleOptionParser;\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotator\\ClassAnnotator;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\PathAwareClassAnnotatorTaskInterface;\nuse IdeHelper\\Annotator\\ClassAnnotatorTaskCollection;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass ClassesCommand extends AnnotateCommand {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate classes using class annotation tasks. This task is not part of \"all\" when \"-r\" is used.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\ConsoleOptionParser $parser\n\t * @return \\Cake\\Console\\ConsoleOptionParser\n\t */\n\tprotected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser {\n\t\t$parser = parent::buildOptionParser($parser);\n\t\t$parser->removeOption('remove');\n\n\t\treturn $parser;\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$paths = $this->getPaths('classes');\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\t$this->setPlugin($plugin);\n\t\t\tforeach ($pluginPaths as $path) {\n\t\t\t\t$folders = glob($path . '*', GLOB_ONLYDIR) ?: [];\n\t\t\t\tforeach ($folders as $folder) {\n\t\t\t\t\t$this->_classes($folder . DS);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t$collection = new ClassAnnotatorTaskCollection();\n\t\t$tasks = $collection->defaultTasks();\n\n\t\t$this->_walkPathAwareTasks($tasks);\n\n\t\tif ($args->getOption('ci') && $this->_annotatorMadeChanges()) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * Walk every directory declared by a path-aware annotator task, in app\n\t * context and (when `-p <plugin>` is used) per-plugin. The standard\n\t * ClassAnnotator runs over each *.php it finds; tasks gate themselves\n\t * via shouldRun(), so unrelated tasks self-skip these paths.\n\t *\n\t * @param array<class-string<\\IdeHelper\\Annotator\\ClassAnnotatorTask\\ClassAnnotatorTaskInterface>> $tasks\n\t * @return void\n\t */\n\tprotected function _walkPathAwareTasks(array $tasks): void {\n\t\t$pathAware = array_filter(\n\t\t\t$tasks,\n\t\t\tfn (string $cls): bool => is_a($cls, PathAwareClassAnnotatorTaskInterface::class, true),\n\t\t);\n\t\tif (!$pathAware) {\n\t\t\treturn;\n\t\t}\n\n\t\t$paths = $this->getPaths();\n\t\t$walked = [];\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\t$this->setPlugin($plugin);\n\t\t\tforeach ($pluginPaths as $rootPath) {\n\t\t\t\tforeach ($pathAware as $taskClass) {\n\t\t\t\t\tforeach ($taskClass::scanPaths() as $relPath) {\n\t\t\t\t\t\t$folder = $rootPath . $this->_normalizeScanPath($relPath);\n\t\t\t\t\t\tif (isset($walked[$folder]) || !is_dir($folder)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$walked[$folder] = true;\n\t\t\t\t\t\t$this->_classes($folder);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Normalize a scan path declared by a path-aware task to the OS-native\n\t * separator with exactly one trailing separator, regardless of whether\n\t * the task author used forward slashes, backslashes, or no trailing\n\t * separator. This keeps the `$walked[$folder]` dedup key stable across\n\t * portability quirks.\n\t *\n\t * @param string $relPath\n\t * @return string\n\t */\n\tprotected function _normalizeScanPath(string $relPath): string {\n\t\t$forward = str_replace('\\\\', '/', $relPath);\n\t\t$trimmed = rtrim($forward, '/');\n\n\t\treturn str_replace('/', DS, $trimmed) . DS;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @return void\n\t */\n\tprotected function _classes(string $folder) {\n\t\t$this->io->out(str_replace(ROOT . DS, '', $folder), 1, ConsoleIo::VERBOSE);\n\n\t\t$folderContent = glob($folder . '*') ?: [];\n\t\tforeach ($folderContent as $path) {\n\n\t\t\t// Prevent infinite loop\n\t\t\tif ($folder === $path) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (is_dir($path)) {\n\t\t\t\t$folderName = pathinfo($path, PATHINFO_BASENAME);\n\t\t\t\t$prefixes = (array)Configure::read('IdeHelper.prefixes') ?: null;\n\n\t\t\t\tif ($prefixes !== null && !in_array($folderName, $prefixes, true)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$this->_classes($path . DS);\n\t\t\t} else {\n\t\t\t\t$extension = pathinfo($path, PATHINFO_EXTENSION);\n\t\t\t\tif ($extension !== 'php') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\t\t\tif ($this->_shouldSkip($name, $path)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$this->io->out('-> ' . $name, 1, ConsoleIo::VERBOSE);\n\n\t\t\t\t$annotator = $this->getAnnotator(ClassAnnotator::class);\n\t\t\t\t$annotator->annotate($path);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/CommandsCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Annotator\\CommandAnnotator;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass CommandsCommand extends AnnotateCommand {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate primary model as well as used models in commands.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$paths = $this->getPaths('Command');\n\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\t$this->setPlugin($plugin);\n\t\t\tforeach ($pluginPaths as $path) {\n\t\t\t\t$this->_commands($path);\n\t\t\t}\n\t\t}\n\n\t\tif ($args->getOption('ci') && $this->_annotatorMadeChanges()) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @return void\n\t */\n\tprotected function _commands(string $folder) {\n\t\t$this->io->out(str_replace(ROOT . DS, '', $folder), 1, ConsoleIo::VERBOSE);\n\n\t\t$folderContent = glob($folder . '*') ?: [];\n\t\tforeach ($folderContent as $path) {\n\t\t\tif (is_dir($path)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\t\tif ($this->_shouldSkip($name, $path)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$this->io->out('-> ' . $name, 1, ConsoleIo::VERBOSE);\n\t\t\t$annotator = $this->getAnnotator(CommandAnnotator::class);\n\t\t\t$annotator->annotate($path);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/ComponentsCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Annotator\\ComponentAnnotator;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass ComponentsCommand extends AnnotateCommand {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate used components inside components.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$paths = $this->getPaths('Controller/Component');\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\t$this->setPlugin($plugin);\n\t\t\tforeach ($pluginPaths as $path) {\n\t\t\t\t$this->_components($path);\n\t\t\t}\n\t\t}\n\n\t\tif ($args->getOption('ci') && $this->_annotatorMadeChanges()) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @return void\n\t */\n\tprotected function _components(string $folder) {\n\t\t$this->io->out(str_replace(ROOT . DS, '', $folder), 1, ConsoleIo::VERBOSE);\n\n\t\t$folderContent = glob($folder . '*') ?: [];\n\t\tforeach ($folderContent as $path) {\n\t\t\tif (is_dir($path)) {\n\t\t\t\t$this->_components($path);\n\t\t\t} else {\n\t\t\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\t\t\tif ($this->_shouldSkip($name, $path)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$this->io->out('-> ' . $name, 1, ConsoleIo::VERBOSE);\n\t\t\t\t$annotator = $this->getAnnotator(ComponentAnnotator::class);\n\t\t\t\t$annotator->annotate($path);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/ControllersCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotator\\ControllerAnnotator;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass ControllersCommand extends AnnotateCommand {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate primary model as well as used models in controller class.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$paths = $this->getPaths('Controller');\n\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\t$this->setPlugin($plugin);\n\t\t\tforeach ($pluginPaths as $path) {\n\t\t\t\t$this->_controllers($path);\n\t\t\t}\n\t\t}\n\n\t\tif ($args->getOption('ci') && $this->_annotatorMadeChanges()) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @return void\n\t */\n\tprotected function _controllers(string $folder) {\n\t\t$this->io->out(str_replace(ROOT . DS, '', $folder), 1, ConsoleIo::VERBOSE);\n\n\t\t$folderContent = glob($folder . '*') ?: [];\n\t\tforeach ($folderContent as $path) {\n\n\t\t\tif (is_dir($path)) {\n\t\t\t\t$subFolder = pathinfo($path, PATHINFO_BASENAME);\n\t\t\t\tif ($subFolder === 'Component') {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$prefixes = (array)Configure::read('IdeHelper.prefixes') ?: null;\n\n\t\t\t\tif ($prefixes !== null && !in_array($subFolder, $prefixes, true)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$this->_controllers($folder . $subFolder . DS);\n\t\t\t} else {\n\t\t\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\t\t\tif ($this->_shouldSkip($name, $path)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$this->io->out('-> ' . $name, 1, ConsoleIo::VERBOSE);\n\n\t\t\t\t$annotator = $this->getAnnotator(ControllerAnnotator::class);\n\t\t\t\t$annotator->annotate($path);\n\t\t\t}\n\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/HelpersCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Annotator\\HelperAnnotator;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass HelpersCommand extends AnnotateCommand {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate used helpers inside helpers.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$paths = $this->getPaths('View/Helper');\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\t$this->setPlugin($plugin);\n\t\t\tforeach ($pluginPaths as $path) {\n\t\t\t\t$this->_helpers($path);\n\t\t\t}\n\t\t}\n\n\t\tif ($args->getOption('ci') && $this->_annotatorMadeChanges()) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @return void\n\t */\n\tprotected function _helpers($folder) {\n\t\t$this->io->out(str_replace(ROOT . DS, '', $folder), 1, ConsoleIo::VERBOSE);\n\n\t\t$folderContent = glob($folder . '*') ?: [];\n\t\tforeach ($folderContent as $path) {\n\t\t\tif (is_dir($path)) {\n\t\t\t\t$this->_helpers($path);\n\t\t\t} else {\n\t\t\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\t\t\tif ($this->_shouldSkip($name, $path)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$this->io->out('-> ' . $name, 1, ConsoleIo::VERBOSE);\n\t\t\t\t$annotator = $this->getAnnotator(HelperAnnotator::class);\n\t\t\t\t$annotator->annotate($path);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/ModelsCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Annotator\\ModelAnnotator;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass ModelsCommand extends AnnotateCommand {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate fields and relations in table and entity class.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$paths = $this->getPaths('Model/Table');\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\tforeach ($pluginPaths as $path) {\n\t\t\t\t$this->setPlugin($plugin);\n\t\t\t\t$this->_models($path);\n\t\t\t}\n\t\t}\n\n\t\tif ($args->getOption('ci') && $this->_annotatorMadeChanges()) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @return void\n\t */\n\tprotected function _models(string $folder) {\n\t\t$this->io->out(str_replace(ROOT . DS, '', $folder), 1, ConsoleIo::VERBOSE);\n\n\t\t$folderContent = glob($folder . '*') ?: [];\n\t\tforeach ($folderContent as $path) {\n\t\t\tif (!is_file($path)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\t\tif ($this->_shouldSkip($name, $path)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$this->io->out('-> ' . $name, 1, ConsoleIo::VERBOSE);\n\n\t\t\t$annotator = $this->getAnnotator(ModelAnnotator::class);\n\t\t\t$annotator->annotate($path);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/TemplatesCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Annotator\\TemplateAnnotator;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass TemplatesCommand extends AnnotateCommand {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate helpers in view templates and elements.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$paths = $this->getPaths('templates');\n\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\t$this->setPlugin($plugin);\n\t\t\tforeach ($pluginPaths as $path) {\n\t\t\t\t$this->_templates($path);\n\t\t\t}\n\t\t}\n\n\t\tif ($args->getOption('ci') && $this->_annotatorMadeChanges()) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @return void\n\t */\n\tprotected function _templates($folder) {\n\t\t$this->io->out(str_replace(ROOT . DS, '', $folder), 1, ConsoleIo::VERBOSE);\n\n\t\t$folderContent = glob($folder . '*') ?: [];\n\t\tforeach ($folderContent as $path) {\n\n\t\t\t// Prevent infinite loop\n\t\t\tif ($folder === $path) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (is_dir($path)) {\n\t\t\t\tforeach ($this->_config['skipTemplatePaths'] as $skip) {\n\t\t\t\t\t$subFolder = pathinfo($path, PATHINFO_BASENAME);\n\t\t\t\t\tif (!str_contains($subFolder, $skip)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ($this->args->getOption('verbose')) {\n\t\t\t\t\t\t$this->io->warning(sprintf('Skipped template folder `%s`', str_replace(ROOT . DS, '', $subFolder)));\n\t\t\t\t\t}\n\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t$this->_templates($path . DS);\n\t\t\t} else {\n\t\t\t\t$extension = pathinfo($path, PATHINFO_EXTENSION);\n\t\t\t\tif ($this->_shouldSkipExtension($extension)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$name = pathinfo($path, PATHINFO_FILENAME);\n\t\t\t\t$dir = $name;\n\t\t\t\t$templatePathPos = strpos($folder, DS . 'templates' . DS);\n\t\t\t\tif ($templatePathPos) {\n\t\t\t\t\t$dir = substr($folder, $templatePathPos + strlen(DS . 'templates' . DS)) . $name;\n\t\t\t\t}\n\t\t\t\tif ($this->_shouldSkip($dir, $path)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$this->io->out('-> ' . $name, 1, ConsoleIo::VERBOSE);\n\t\t\t\t$annotator = $this->getAnnotator(TemplateAnnotator::class);\n\t\t\t\t$annotator->annotate($path);\n\t\t\t}\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Annotate/ViewCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command\\Annotate;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Core\\App;\nuse IdeHelper\\Annotator\\ViewAnnotator;\nuse IdeHelper\\Command\\AnnotateCommand;\n\nclass ViewCommand extends AnnotateCommand {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Annotate used helpers in AppView.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t * @return int\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\tif ($args->getOption('plugin') || $args->getOption('filter')) {\n\t\t\t$io->error('Plugin or filter option not supported for this command');\n\t\t\t$this->abort();\n\t\t}\n\n\t\t$className = App::className('App', 'View', 'View');\n\t\t$file = APP . 'View' . DS . 'AppView.php';\n\t\tif (!$className || !file_exists($file)) {\n\t\t\t$io->warning('You need to create `AppView.php` first in `' . APP_DIR . DS . 'View' . DS . '`.');\n\n\t\t\treturn static::CODE_SUCCESS;\n\t\t}\n\n\t\t$folder = pathinfo($file, PATHINFO_DIRNAME);\n\t\t$io->out(str_replace(ROOT . DS, '', $folder));\n\t\t$io->out(' -> ' . pathinfo($file, PATHINFO_BASENAME));\n\n\t\t$annotator = $this->getAnnotator(ViewAnnotator::class);\n\t\t$annotator->annotate($file);\n\n\t\tif ($args->getOption('ci') && $this->_annotatorMadeChanges()) {\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/AnnotateCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Console\\ConsoleOptionParser;\nuse Cake\\Core\\Configure;\nuse Cake\\TestSuite\\ConnectionHelper;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Console\\Io;\nuse RuntimeException;\n\nabstract class AnnotateCommand extends Command {\n\n\t/**\n\t * @var int\n\t */\n\tpublic const CODE_CHANGES = 2;\n\n\t/**\n\t * @var array<string>\n\t */\n\tpublic const TEMPLATE_EXTENSIONS = ['php'];\n\n\t/**\n\t * @var array<string, mixed>\n\t */\n\tprotected array $_config = [\n\t\t'skipTemplatePaths' => [\n\t\t\t'/templates/Bake/',\n\t\t],\n\t];\n\n\t/**\n\t * @return void\n\t */\n\tpublic function initialize(): void {\n\t\tparent::initialize();\n\n\t\t$skip = (array)Configure::read('IdeHelper.skipTemplatePaths');\n\t\tif ($skip) {\n\t\t\t$this->_config['skipTemplatePaths'] = $skip;\n\t\t}\n\t}\n\n\t/**\n\t * E.g.:\n\t * bin/cake upgrade /path/to/app --level=cakephp40\n\t *\n\t * @param \\Cake\\Console\\Arguments $args The command arguments.\n\t * @param \\Cake\\Console\\ConsoleIo $io The console io\n\t *\n\t * @throws \\Cake\\Console\\Exception\\StopException\n\t * @return int|null|void The exit code or null for success\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io) {\n\t\tparent::execute($args, $io);\n\n\t\tif ($args->getOption('ci')) {\n\t\t\tif (!$args->getOption('dry-run') || $args->getOption('interactive')) {\n\t\t\t\t$io->error('Continuous Integration mode requires -d param as well as no -i param!');\n\t\t\t\t$this->abort();\n\t\t\t}\n\t\t\tConnectionHelper::addTestAliases();\n\t\t}\n\n\t\tAbstractAnnotator::$output = false;\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\ConsoleOptionParser $parser The parser to be defined\n\t *\n\t * @return \\Cake\\Console\\ConsoleOptionParser The built parser.\n\t */\n\tprotected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser {\n\t\t$options = [\n\t\t\t'dry-run' => [\n\t\t\t\t'short' => 'd',\n\t\t\t\t'help' => 'Dry run the task(s). Don\\'t modify any files.',\n\t\t\t\t'boolean' => true,\n\t\t\t],\n\t\t\t'plugin' => [\n\t\t\t\t'short' => 'p',\n\t\t\t\t'help' => 'The plugin(s) to run. Defaults to the application otherwise. Supports wildcard `*` for partial match, `all` for all app plugins.',\n\t\t\t\t'default' => null,\n\t\t\t],\n\t\t\t'remove' => [\n\t\t\t\t'short' => 'r',\n\t\t\t\t'help' => 'Remove outdated annotations. Make sure you commited first or have a backup!',\n\t\t\t\t'boolean' => true,\n\t\t\t],\n\t\t\t'filter' => [\n\t\t\t\t'short' => 'f',\n\t\t\t\t'help' => 'Filter by search string in file name. For templates also in path.',\n\t\t\t\t'default' => null,\n\t\t\t],\n\t\t\t'file' => [\n\t\t\t\t'help' => 'Pass file(s) to run for, comma separated. Can be absolute or ROOT relative.',\n\t\t\t\t'default' => null,\n\t\t\t],\n\t\t\t'ci' => [\n\t\t\t\t'help' => 'Enable CI mode (requires dry-run). This will return an error code ' . static::CODE_CHANGES . ' if changes are necessary.',\n\t\t\t\t'boolean' => true,\n\t\t\t],\n\t\t\t'interactive' => [\n\t\t\t\t'short' => 'i',\n\t\t\t\t'help' => 'Interactive mode (prompt before each type).',\n\t\t\t\t'boolean' => true,\n\t\t\t],\n\t\t];\n\n\t\t$parser->addOptions($options);\n\n\t\treturn $parser->setDescription('Annotation Command for generating better IDE auto-complete/hinting.');\n\t}\n\n\t/**\n\t * @return \\IdeHelper\\Console\\Io\n\t */\n\tprotected function _io(): Io {\n\t\treturn new Io($this->io);\n\t}\n\n\t/**\n\t * @param string $fileName\n\t * @param string|null $path\n\t *\n\t * @return bool\n\t */\n\tprotected function _shouldSkip(string $fileName, ?string $path = null): bool {\n\t\t$files = $this->_files();\n\t\tif ($files) {\n\t\t\treturn !in_array($path, $files, true);\n\t\t}\n\n\t\t$filter = (string)$this->args->getOption('filter');\n\t\tif (!$filter) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn !preg_match('/' . preg_quote($filter, '/') . '/i', $fileName);\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function _files(): array {\n\t\t$file = (string)$this->args->getOption('file');\n\t\tif (!$file) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$files = explode(',', $file);\n\t\tforeach ($files as $k => $file) {\n\t\t\tif (!str_starts_with($file, ROOT . DS)) {\n\t\t\t\t$file = ROOT . DS . $file;\n\t\t\t}\n\n\t\t\tif (!file_exists($file)) {\n\t\t\t\tthrow new RuntimeException('Cannot find file: ' . $file);\n\t\t\t}\n\n\t\t\t$files[$k] = $file;\n\t\t}\n\n\t\treturn $files;\n\t}\n\n\t/**\n\t * Checks template extensions against whitelist.\n\t *\n\t * @param string $extension\n\t * @return bool\n\t */\n\tprotected function _shouldSkipExtension(string $extension): bool {\n\t\t$whitelist = Configure::read('IdeHelper.templateExtensions') ?: static::TEMPLATE_EXTENSIONS;\n\n\t\treturn !in_array($extension, $whitelist, true);\n\t}\n\n\t/**\n\t * @param class-string<\\IdeHelper\\Annotator\\AbstractAnnotator> $class\n\t *\n\t * @return \\IdeHelper\\Annotator\\AbstractAnnotator\n\t */\n\tprotected function getAnnotator(string $class): AbstractAnnotator {\n\t\t/** @phpstan-var array<class-string<\\IdeHelper\\Annotator\\AbstractAnnotator>> $tasks */\n\t\t$tasks = (array)Configure::read('IdeHelper.annotators');\n\t\tif (isset($tasks[$class])) {\n\t\t\t$class = $tasks[$class];\n\t\t}\n\n\t\t$options = $this->args->getOptions();\n\t\t$options['plugin'] = $this->plugin;\n\n\t\treturn new $class($this->_io(), $options);\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tprotected function _annotatorMadeChanges(): bool {\n\t\treturn AbstractAnnotator::$output !== false;\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/Command.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command;\n\nuse Cake\\Command\\Command as CoreCommand;\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\Utility\\PluginPath;\n\nabstract class Command extends CoreCommand {\n\n\t/**\n\t * @var \\Cake\\Console\\Arguments\n\t */\n\tprotected Arguments $args;\n\n\t/**\n\t * @var \\Cake\\Console\\ConsoleIo\n\t */\n\tprotected ConsoleIo $io;\n\n\tprotected ?string $plugin = null;\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args The command arguments.\n\t * @param \\Cake\\Console\\ConsoleIo $io The console io\n\t * @throws \\Cake\\Console\\Exception\\StopException\n\t * @return int|null|void The exit code or null for success\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io) {\n\t\t$this->args = $args;\n\t\t$this->io = $io;\n\n\t\tparent::execute($args, $io);\n\t}\n\n\t/**\n\t * @param string|null $type\n\t * @return array<string, array<string>>\n\t */\n\tprotected function getPaths(?string $type = null): array {\n\t\t$plugin = (string)$this->args->getOption('plugin') ?: null;\n\t\tif (!$plugin) {\n\t\t\tif (!$type) {\n\t\t\t\t$paths = [ROOT . DS];\n\t\t\t} elseif ($type === 'classes') {\n\t\t\t\t$paths = [ROOT . DS . APP_DIR . DS];\n\t\t\t} else {\n\t\t\t\t$paths = $type === 'templates' ? App::path('templates') : AppPath::get($type);\n\t\t\t}\n\n\t\t\treturn ['app' => $paths];\n\t\t}\n\n\t\t$plugins = $this->getPlugins($plugin);\n\n\t\t$paths = [];\n\t\tforeach ($plugins as $plugin) {\n\t\t\tif (!$type) {\n\t\t\t\t$pluginPaths = [Plugin::path($plugin)];\n\t\t\t} else {\n\t\t\t\tif ($type === 'classes') {\n\t\t\t\t\t$pluginPaths = [PluginPath::classPath($plugin)];\n\t\t\t\t} else {\n\t\t\t\t\t$pluginPaths = $type === 'templates' ? App::path('templates', $plugin) : AppPath::get($type, $plugin);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$paths[$plugin] = $pluginPaths;\n\t\t}\n\n\t\treturn $paths;\n\t}\n\n\t/**\n\t * @param string $plugin\n\t *\n\t * @return array<string>\n\t */\n\tprotected function getPlugins(string $plugin): array {\n\t\tif ($plugin !== 'all' && !str_contains($plugin, '*')) {\n\t\t\treturn [Plugin::path($plugin) => $plugin];\n\t\t}\n\n\t\t$loaded = Plugin::loaded();\n\t\t$plugins = [];\n\t\tforeach ($loaded as $name) {\n\t\t\t$path = Plugin::path($name);\n\t\t\t$rootPath = str_replace(ROOT . DS, '', $path);\n\t\t\tif (str_starts_with($rootPath, 'vendor' . DS)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$plugins[$path] = $name;\n\t\t}\n\n\t\tif ($plugin === 'all') {\n\t\t\treturn $plugins;\n\t\t}\n\n\t\treturn $this->filterPlugins($plugins, $plugin);\n\t}\n\n\t/**\n\t * @param array<string> $plugins\n\t * @param string $pattern\n\t * @return array<string>\n\t */\n\tprotected function filterPlugins(array $plugins, string $pattern): array {\n\t\treturn array_filter($plugins, function($plugin) use ($pattern) {\n\t\t\treturn fnmatch($pattern, $plugin);\n\t\t});\n\t}\n\n\t/**\n\t * @param string $plugin\n\t * @return void\n\t */\n\tprotected function setPlugin(string $plugin): void {\n\t\tif (!$plugin || $plugin === 'app') {\n\t\t\t$this->plugin = null;\n\n\t\t\treturn;\n\t\t}\n\n\t\t$this->plugin = $plugin;\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/GenerateCodeCompletionCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command;\n\nuse Cake\\Command\\Command;\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Console\\ConsoleOptionParser;\nuse IdeHelper\\CodeCompletion\\CodeCompletionGenerator;\nuse IdeHelper\\CodeCompletion\\TaskCollection;\n\nclass GenerateCodeCompletionCommand extends Command {\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'CodeCompletion File Generator for generating better IDE auto-complete/hinting.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args The command arguments.\n\t * @param \\Cake\\Console\\ConsoleIo $io The console io\n\t *\n\t * @throws \\Cake\\Console\\Exception\\StopException\n\t * @return int The exit code or null for success\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\t$codeCompletionGenerator = $this->getGenerator();\n\n\t\tif ($args->getOption('dry-run')) {\n\t\t\treturn static::CODE_SUCCESS;\n\t\t}\n\n\t\t$types = $codeCompletionGenerator->generate();\n\n\t\t$io->out('CodeCompletion files generated: ' . implode(', ', $types));\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\ConsoleOptionParser $parser The parser to be defined\n\t *\n\t * @return \\Cake\\Console\\ConsoleOptionParser The built parser.\n\t */\n\tprotected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser {\n\t\t$parser = parent::buildOptionParser($parser);\n\t\t$options = [\n\t\t\t'dry-run' => [\n\t\t\t\t'short' => 'd',\n\t\t\t\t'help' => 'Dry run the generation. This will not actually generate any files.',\n\t\t\t\t'boolean' => true,\n\t\t\t],\n\t\t];\n\n\t\treturn $parser\n\t\t\t->setDescription(static::getDescription())\n\t\t\t->addOptions($options);\n\t}\n\n\t/**\n\t * @return \\IdeHelper\\CodeCompletion\\CodeCompletionGenerator\n\t */\n\tprotected function getGenerator(): CodeCompletionGenerator {\n\t\t$taskCollection = new TaskCollection();\n\n\t\treturn new CodeCompletionGenerator($taskCollection);\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/GeneratePhpStormMetaCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command;\n\nuse Cake\\Command\\Command;\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Console\\ConsoleOptionParser;\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\Generator\\PhpstormGenerator;\nuse IdeHelper\\Generator\\TaskCollection;\nuse RuntimeException;\n\nclass GeneratePhpStormMetaCommand extends Command {\n\n\t/**\n\t * @var int\n\t */\n\tpublic const CODE_CHANGES = 2;\n\n\t/**\n\t * @var \\Cake\\Console\\ConsoleIo\n\t */\n\tprotected ConsoleIo $io;\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'Meta File Generator for generating better IDE auto-complete/hinting in PhpStorm.';\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args The command arguments.\n\t * @param \\Cake\\Console\\ConsoleIo $io The console io\n\t *\n\t * @throws \\Cake\\Console\\Exception\\StopException\n\t * @return int The exit code or null for success\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\t$this->io = $io;\n\n\t\tparent::execute($args, $io);\n\n\t\t$phpstormGenerator = $this->getGenerator();\n\t\t$content = $phpstormGenerator->generate();\n\n\t\t$filePath = $this->getMetaFilePath();\n\n\t\t$unchanged = file_exists($filePath) && md5_file($filePath) === md5($content);\n\t\tif ($unchanged) {\n\t\t\t$io->out('Meta file `/.phpstorm.meta.php/.ide-helper.meta.php` still up to date.');\n\n\t\t\treturn parent::CODE_SUCCESS;\n\t\t}\n\n\t\tif ($args->getOption('dry-run')) {\n\t\t\t$io->out('Meta file `/.phpstorm.meta.php/.ide-helper.meta.php` needs updating.');\n\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\t$this->ensureDir();\n\n\t\tif (file_put_contents($filePath, $content) === false) {\n\t\t\tthrow new RuntimeException(sprintf('Failed to write meta file `%s`.', $filePath));\n\t\t}\n\n\t\t$io->out('Meta file `/.phpstorm.meta.php/.ide-helper.meta.php` generated.');\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\ConsoleOptionParser $parser The parser to be defined\n\t *\n\t * @return \\Cake\\Console\\ConsoleOptionParser The built parser.\n\t */\n\tprotected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser {\n\t\t$subcommandParser = [\n\t\t\t'dry-run' => [\n\t\t\t\t'short' => 'd',\n\t\t\t\t'help' => 'Dry run the generation. This will output an error code ' . static::CODE_CHANGES . ' if file needs changing. Can be used for CI checking.',\n\t\t\t\t'boolean' => true,\n\t\t\t],\n\t\t];\n\n\t\t$details = 'Generate `/.phpstorm.meta.php/.ide-helper.meta.php` meta file.';\n\n\t\t$parser->addOptions($subcommandParser);\n\n\t\treturn $parser\n\t\t\t->setDescription(static::getDescription() . PHP_EOL . $details);\n\t}\n\n\t/**\n\t * @return \\IdeHelper\\Generator\\PhpstormGenerator\n\t */\n\tprotected function getGenerator(): PhpstormGenerator {\n\t\t$taskCollection = new TaskCollection();\n\n\t\treturn new PhpstormGenerator($taskCollection, $this->io());\n\t}\n\n\t/**\n\t * @throws \\RuntimeException\n\t * @return string\n\t */\n\tprotected function getMetaFilePath(): string {\n\t\tif (is_file(ROOT . DS . '.phpstorm.meta.php')) {\n\t\t\tthrow new RuntimeException('Please use a directory called `ROOT/.phpstorm.meta.php/` and store your custom files there. Remove any root file you still have.');\n\t\t}\n\n\t\treturn ROOT . DS . '.phpstorm.meta.php' . DS . '.ide-helper.meta.php';\n\t}\n\n\t/**\n\t * @throws \\RuntimeException When the directory cannot be created.\n\t * @return void\n\t */\n\tprotected function ensureDir(): void {\n\t\t$dir = dirname($this->getMetaFilePath());\n\t\tif (!is_dir($dir) && !mkdir($dir, 0775, true) && !is_dir($dir)) {\n\t\t\tthrow new RuntimeException(sprintf('Cannot create directory `%s`.', $dir));\n\t\t}\n\t}\n\n\t/**\n\t * @return \\IdeHelper\\Console\\Io\n\t */\n\tprotected function io(): Io {\n\t\treturn new Io($this->io);\n\t}\n\n}\n"
  },
  {
    "path": "src/Command/IlluminateCommand.php",
    "content": "<?php\n\nnamespace IdeHelper\\Command;\n\nuse Cake\\Console\\Arguments;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Console\\ConsoleOptionParser;\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\Illuminator\\Illuminator;\nuse IdeHelper\\Illuminator\\TaskCollection;\n\nclass IlluminateCommand extends Command {\n\n\t/**\n\t * @var int\n\t */\n\tpublic const CODE_CHANGES = 2;\n\n\t/**\n\t * @return string\n\t */\n\tpublic static function getDescription(): string {\n\t\treturn 'PHP file modifier.';\n\t}\n\n\t/**\n\t * E.g.:\n\t * bin/cake upgrade /path/to/app --level=cakephp40\n\t *\n\t * @param \\Cake\\Console\\Arguments $args The command arguments.\n\t * @param \\Cake\\Console\\ConsoleIo $io The console io\n\t *\n\t * @throws \\Cake\\Console\\Exception\\StopException\n\t * @return int The exit code or null for success\n\t */\n\tpublic function execute(Arguments $args, ConsoleIo $io): int {\n\t\tparent::execute($args, $io);\n\n\t\t$paths = $this->getPaths();\n\n\t\t$pathElement = $args->getArgument('path');\n\t\tif (!$pathElement) {\n\t\t\t$pathElement = ($args->getOption('plugin') ? 'src' : APP_DIR) . DS;\n\t\t}\n\n\t\t$filesChanged = 0;\n\t\tforeach ($paths as $plugin => $pluginPaths) {\n\t\t\t$this->setPlugin($plugin);\n\t\t\tforeach ($pluginPaths as $path) {\n\t\t\t\t$path .= $pathElement;\n\t\t\t\tif (!is_dir($path)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$illuminator = $this->getIlluminator($args);\n\t\t\t\t$filesChanged += $illuminator->illuminate($path, (string)$args->getOption('filter') ?: null);\n\t\t\t}\n\t\t}\n\n\t\tif (!$filesChanged) {\n\t\t\treturn static::CODE_SUCCESS;\n\t\t}\n\n\t\tif ($args->getOption('dry-run')) {\n\t\t\t$io->out($filesChanged . ' files need(s) updating.');\n\n\t\t\treturn static::CODE_CHANGES;\n\t\t}\n\n\t\t$io->out('Files updated: ' . $filesChanged);\n\n\t\treturn static::CODE_SUCCESS;\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\ConsoleOptionParser $parser The parser to be defined\n\t *\n\t * @return \\Cake\\Console\\ConsoleOptionParser The built parser.\n\t */\n\tprotected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser {\n\t\t$tasks = $this->getTaskList();\n\n\t\t$subcommandParser = [\n\t\t\t'plugin' => [\n\t\t\t\t'short' => 'p',\n\t\t\t\t'help' => 'The plugin(s) to run. Defaults to the application otherwise. Supports wildcard `*` for partial match, `all` for all app plugins.',\n\t\t\t\t'default' => null,\n\t\t\t],\n\t\t\t'dry-run' => [\n\t\t\t\t'short' => 'd',\n\t\t\t\t'help' => 'Dry run the task(s). This will output an error code ' . static::CODE_CHANGES . ' if file needs changing. Can be used for CI checking.',\n\t\t\t\t'boolean' => true,\n\t\t\t],\n\t\t\t'task' => [\n\t\t\t\t'short' => 't',\n\t\t\t\t'help' => 'Run specific task(s). Can be comma separated list. Available: ' . implode(', ', $tasks),\n\t\t\t\t'default' => null,\n\t\t\t],\n\t\t\t'filter' => [\n\t\t\t\t'short' => 'f',\n\t\t\t\t'help' => 'Filter by search string in file name.',\n\t\t\t\t'default' => null,\n\t\t\t],\n\t\t];\n\n\t\t$parser->addOptions($subcommandParser);\n\t\t$parser->addArgument('path', [\n\t\t\t'name' => 'path',\n\t\t\t'help' => 'Path in your project or plugin. Defaults to src/',\n\t\t\t'required' => false,\n\t\t]);\n\n\t\t$taskList = 'Tasks: ' . implode(', ', $tasks);\n\t\t$descr = static::getDescription() . PHP_EOL . 'Run Illuminator tasks over your PHP files.' . PHP_EOL;\n\n\t\treturn $parser->setDescription($descr . $taskList);\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\Arguments $args\n\t *\n\t * @return \\IdeHelper\\Illuminator\\Illuminator\n\t */\n\tprotected function getIlluminator(Arguments $args): Illuminator {\n\t\t$tasks = $args->getOption('task') ? explode(',', (string)$args->getOption('task')) : [];\n\n\t\t$options = $args->getOptions();\n\t\t$options['plugin'] = $this->plugin;\n\n\t\t$taskCollection = new TaskCollection($this->io(), $options, $tasks);\n\n\t\treturn new Illuminator($taskCollection);\n\t}\n\n\t/**\n\t * @throws \\RuntimeException\n\t * @throws \\InvalidArgumentException\n\t * @return array<string>\n\t */\n\tprotected function getTaskList(): array {\n\t\t$taskCollection = new TaskCollection($this->io(), []);\n\n\t\treturn $taskCollection->taskNames();\n\t}\n\n\t/**\n\t * @return \\IdeHelper\\Console\\Io\n\t */\n\tprotected function io(): Io {\n\t\t$io = $this->io ?? new ConsoleIo();\n\n\t\treturn new Io($io);\n\t}\n\n}\n"
  },
  {
    "path": "src/Console/Io.php",
    "content": "<?php\n\nnamespace IdeHelper\\Console;\n\nuse Cake\\Command\\Command;\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Console\\Exception\\StopException;\n\n/**\n * Composition class as proxy towards ConsoleIO - basically a shell replacement for inside business logic.\n */\nclass Io {\n\n\tprotected ConsoleIo $_io;\n\n\t/**\n\t * Output at the verbose level.\n\t *\n\t * @param array<string>|string $message A string or an array of strings to output\n\t * @param int $newlines Number of newlines to append\n\t * @return int|null The number of bytes returned from writing to stdout.\n\t */\n\tpublic function verbose(array|string $message, int $newlines = 1): ?int {\n\t\treturn $this->_io->verbose($message, $newlines);\n\t}\n\n\t/**\n\t * @param \\Cake\\Console\\ConsoleIo $io\n\t */\n\tpublic function __construct(ConsoleIo $io) {\n\t\t$this->_io = $io;\n\t}\n\n\t/**\n\t * Output at all levels.\n\t *\n\t * @param array<string>|string $message A string or an array of strings to output\n\t * @param int $newlines Number of newlines to append\n\t * @return int|null The number of bytes returned from writing to stdout.\n\t */\n\tpublic function quiet(array|string $message, int $newlines = 1): ?int {\n\t\treturn $this->_io->quiet($message, $newlines);\n\t}\n\n\t/**\n\t * Outputs a single or multiple messages to stdout. If no parameters\n\t * are passed outputs just a newline.\n\t *\n\t * ### Output levels\n\t *\n\t * There are 3 built-in output levels: ConsoleIo::QUIET, ConsoleIo::NORMAL, ConsoleIo::VERBOSE.\n\t * The verbose and quiet output levels, map to the `verbose` and `quiet` output switches\n\t * present in most shells. Using ConsoleIo::QUIET for a message means it will always display.\n\t * While using ConsoleIo::VERBOSE means it will only display when verbose output is toggled.\n\t *\n\t * @param array<string>|string $message A string or an array of strings to output\n\t * @param int $newlines Number of newlines to append\n\t * @param int $level The message's output level, see above.\n\t * @return int|null The number of bytes returned from writing to stdout.\n\t */\n\tpublic function out(array|string $message = '', int $newlines = 1, int $level = ConsoleIo::NORMAL): ?int {\n\t\treturn $this->_io->out($message, $newlines, $level);\n\t}\n\n\t/**\n\t * Outputs a single or multiple error messages to stderr. If no parameters\n\t * are passed outputs just a newline.\n\t *\n\t * @param array<string>|string $message A string or an array of strings to output\n\t * @param int $newlines Number of newlines to append\n\t * @return int|null The number of bytes returned from writing to stderr.\n\t */\n\tpublic function error(array|string $message = '', int $newlines = 1): ?int {\n\t\treturn $this->_io->error($message, $newlines);\n\t}\n\n\t/**\n\t * Outputs a single or multiple error messages to stderr. If no parameters\n\t * are passed outputs just a newline.\n\t *\n\t * @deprecated Use error() instead.\n\t * @param array<string>|string $message A string or an array of strings to output\n\t * @param int $newlines Number of newlines to append\n\t * @return int|null The number of bytes returned from writing to stderr.\n\t */\n\tpublic function err(array|string $message = '', int $newlines = 1): ?int {\n\t\treturn $this->_io->error($message, $newlines);\n\t}\n\n\t/**\n\t * Convenience method for out() that wraps message between <info /> tag\n\t *\n\t * @see http://book.cakephp.org/3.0/en/console-and-shells.html#ConsoleIo::out\n\t * @param array<string>|string $message A string or an array of strings to output\n\t * @param int $newlines Number of newlines to append\n\t * @param int $level The message's output level, see above.\n\t * @return int|null The number of bytes returned from writing to stdout.\n\t */\n\tpublic function info(array|string $message = '', int $newlines = 1, int $level = ConsoleIo::NORMAL): ?int {\n\t\treturn $this->_io->info($message, $newlines, $level);\n\t}\n\n\t/**\n\t * Convenience method for out() that wraps message between <comment /> tag\n\t *\n\t * @see http://book.cakephp.org/3.0/en/console-and-shells.html#ConsoleIo::out\n\t * @param array<string>|string $message A string or an array of strings to output\n\t * @param int $newlines Number of newlines to append\n\t * @param int $level The message's output level, see above.\n\t * @return int|null The number of bytes returned from writing to stdout.\n\t */\n\tpublic function comment(array|string $message = '', int $newlines = 1, int $level = ConsoleIo::NORMAL): ?int {\n\t\treturn $this->_io->comment($message, $newlines, $level);\n\t}\n\n\t/**\n\t * Convenience method for err() that wraps message between <warning /> tag\n\t *\n\t * @see http://book.cakephp.org/3.0/en/console-and-shells.html#ConsoleIo::err\n\t * @param array<string>|string $message A string or an array of strings to output\n\t * @param int $newlines Number of newlines to append\n\t * @return int|null The number of bytes returned from writing to stderr.\n\t */\n\tpublic function warn(array|string $message = '', int $newlines = 1): ?int {\n\t\treturn $this->_io->warning($message, $newlines);\n\t}\n\n\t/**\n\t * Convenience method for out() that wraps message between <success /> tag\n\t *\n\t * @see http://book.cakephp.org/3.0/en/console-and-shells.html#ConsoleIo::out\n\t * @param array<string>|string $message A string or an array of strings to output\n\t * @param int $newlines Number of newlines to append\n\t * @param int $level The message's output level, see above.\n\t * @return int|null The number of bytes returned from writing to stdout.\n\t */\n\tpublic function success(array|string $message = '', int $newlines = 1, int $level = ConsoleIo::NORMAL): ?int {\n\t\treturn $this->_io->success($message, $newlines, $level);\n\t}\n\n\t/**\n\t * Returns a single or multiple linefeeds sequences.\n\t *\n\t * @param int $multiplier Number of times the linefeed sequence should be repeated\n\t * @return string\n\t */\n\tpublic function nl(int $multiplier = 1): string {\n\t\treturn $this->_io->nl($multiplier);\n\t}\n\n\t/**\n\t * Outputs a series of minus characters to the standard output, acts as a visual separator.\n\t *\n\t * @param int $newlines Number of newlines to pre- and append\n\t * @param int $width Width of the line, defaults to 63\n\t * @return void\n\t */\n\tpublic function hr(int $newlines = 0, int $width = 63): void {\n\t\t$this->_io->hr($newlines, $width);\n\t}\n\n\t/**\n\t * Displays a formatted error message\n\t * and exits the application with status code 1\n\t *\n\t * @param string $message The error message\n\t * @param int $exitCode The exit code for the shell task.\n\t * @throws \\Cake\\Console\\Exception\\StopException\n\t * @return void\n\t */\n\tpublic function abort(string $message, int $exitCode = Command::CODE_ERROR): void {\n\t\t$this->_io->error($message);\n\n\t\tthrow new StopException($message, $exitCode);\n\t}\n\n}\n"
  },
  {
    "path": "src/Filesystem/Folder.php",
    "content": "<?php\ndeclare(strict_types=1);\n\n/**\n * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)\n * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)\n *\n * Licensed under The MIT License\n * For full copyright and license information, please see the LICENSE.txt\n * Redistributions of files must retain the above copyright notice.\n *\n * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)\n * @link https://cakephp.org CakePHP(tm) Project\n * @since 0.2.9\n * @license https://opensource.org/licenses/mit-license.php MIT License\n */\n\nnamespace IdeHelper\\Filesystem;\n\nuse DirectoryIterator;\nuse Exception;\nuse InvalidArgumentException;\nuse RecursiveDirectoryIterator;\nuse RecursiveIteratorIterator;\n\n/**\n * Folder structure browser, lists folders and files.\n * Provides an Object interface for Common directory related tasks.\n *\n * @internal\n */\nclass Folder {\n\n\t/**\n\t * Default scheme for Folder::copy\n\t * Recursively merges subfolders with the same name\n\t *\n\t * @var string\n\t */\n\tpublic const MERGE = 'merge';\n\n\t/**\n\t * Overwrite scheme for Folder::copy\n\t * subfolders with the same name will be replaced\n\t *\n\t * @var string\n\t */\n\tpublic const OVERWRITE = 'overwrite';\n\n\t/**\n\t * Skip scheme for Folder::copy\n\t * if a subfolder with the same name exists it will be skipped\n\t *\n\t * @var string\n\t */\n\tpublic const SKIP = 'skip';\n\n\t/**\n\t * Sort mode by name\n\t *\n\t * @var string\n\t */\n\tpublic const SORT_NAME = 'name';\n\n\t/**\n\t * Sort mode by time\n\t *\n\t * @var string\n\t */\n\tpublic const SORT_TIME = 'time';\n\n\t/**\n\t * Path to Folder.\n\t */\n\tpublic ?string $path = null;\n\n\t/**\n\t * Sortedness. Whether list results\n\t * should be sorted by name.\n\t */\n\tpublic bool $sort = false;\n\n\t/**\n\t * Mode to be used on create. Does nothing on windows platforms.\n\t */\n\tpublic int $mode = 0755;\n\n\t/**\n\t * Functions array to be called depending on the sort type chosen.\n\t *\n\t * @var array<string>\n\t */\n\tprotected array $_fsorts = [\n\t\tself::SORT_NAME => 'getPathname',\n\t\tself::SORT_TIME => 'getCTime',\n\t];\n\n\t/**\n\t * Holds messages from last method.\n\t *\n\t * @var array<string>\n\t */\n\tprotected array $_messages = [];\n\n\t/**\n\t * Holds errors from last method.\n\t *\n\t * @var array<string>\n\t */\n\tprotected array $_errors = [];\n\n\t/**\n\t * Constructor.\n\t *\n\t * @param string|null $path Path to folder\n\t * @param bool $create Create folder if not found\n\t * @param int|null $mode Mode (CHMOD) to apply to created folder, false to ignore\n\t */\n\tpublic function __construct(?string $path = null, bool $create = false, ?int $mode = null) {\n\t\tif (!$path) {\n\t\t\t$path = TMP;\n\t\t}\n\t\tif ($mode) {\n\t\t\t$this->mode = $mode;\n\t\t}\n\n\t\tif (!file_exists($path) && $create === true) {\n\t\t\t$this->create($path, $this->mode);\n\t\t}\n\t\tif (!static::isAbsolute($path)) {\n\t\t\t$path = realpath($path);\n\t\t}\n\t\tif (!empty($path)) {\n\t\t\t$this->cd($path);\n\t\t}\n\t}\n\n\t/**\n\t * Return current path.\n\t *\n\t * @return string|null Current path\n\t */\n\tpublic function pwd(): ?string {\n\t\treturn $this->path;\n\t}\n\n\t/**\n\t * Change directory to $path.\n\t *\n\t * @param string $path Path to the directory to change to\n\t * @return string|false The new path. Returns false on failure\n\t */\n\tpublic function cd(string $path) {\n\t\t$path = $this->realpath($path);\n\t\tif ($path !== false && is_dir($path)) {\n\t\t\treturn $this->path = $path;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Returns an array of the contents of the current directory.\n\t * The returned array holds two arrays: One of directories and one of files.\n\t *\n\t * @param string|bool $sort Whether you want the results sorted, set this and the sort property\n\t *   to `false` to get unsorted results.\n\t * @param array<string>|bool $exceptions Either an array or boolean true will not grab dot files\n\t * @param bool $fullPath True returns the full path\n\t * @return array<array<string>> Contents of current directory as an array, an empty array on failure\n\t */\n\tpublic function read($sort = self::SORT_NAME, $exceptions = false, bool $fullPath = false): array {\n\t\t$dirs = $files = [];\n\n\t\tif (!$this->pwd()) {\n\t\t\treturn [$dirs, $files];\n\t\t}\n\t\tif (is_array($exceptions)) {\n\t\t\t$exceptions = array_flip($exceptions);\n\t\t}\n\t\t$skipHidden = isset($exceptions['.']) || $exceptions === true;\n\n\t\ttry {\n\t\t\t$iterator = new DirectoryIterator((string)$this->path);\n\t\t} catch (Exception $e) {\n\t\t\treturn [$dirs, $files];\n\t\t}\n\n\t\tif (!is_bool($sort) && isset($this->_fsorts[$sort])) {\n\t\t\t$methodName = $this->_fsorts[$sort];\n\t\t} else {\n\t\t\t$methodName = $this->_fsorts[static::SORT_NAME];\n\t\t}\n\n\t\tforeach ($iterator as $item) {\n\t\t\tif ($item->isDot()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$name = $item->getFilename();\n\t\t\tif ($skipHidden && $name[0] === '.' || isset($exceptions[$name])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ($fullPath) {\n\t\t\t\t$name = $item->getPathname();\n\t\t\t}\n\n\t\t\tif ($item->isDir()) {\n\t\t\t\t$dirs[$item->{$methodName}()][] = $name;\n\t\t\t} else {\n\t\t\t\t$files[$item->{$methodName}()][] = $name;\n\t\t\t}\n\t\t}\n\n\t\tif ($sort || $this->sort) {\n\t\t\tksort($dirs);\n\t\t\tksort($files);\n\t\t}\n\n\t\tif ($dirs) {\n\t\t\t$dirs = array_merge(...array_values($dirs));\n\t\t}\n\n\t\tif ($files) {\n\t\t\t$files = array_merge(...array_values($files));\n\t\t}\n\n\t\treturn [$dirs, $files];\n\t}\n\n\t/**\n\t * Returns an array of all matching files in current directory.\n\t *\n\t * @param string $regexpPattern Preg_match pattern (Defaults to: .*)\n\t * @param string|bool $sort Whether results should be sorted.\n\t * @return array<string> Files that match given pattern\n\t */\n\tpublic function find(string $regexpPattern = '.*', $sort = false): array {\n\t\t[, $files] = $this->read($sort);\n\n\t\treturn array_values(preg_grep('/^' . $regexpPattern . '$/i', $files) ?: []);\n\t}\n\n\t/**\n\t * Returns an array of all matching files in and below current directory.\n\t *\n\t * @param string $pattern Preg_match pattern (Defaults to: .*)\n\t * @param bool $sort Whether results should be sorted.\n\t * @return array<string> Files matching $pattern\n\t */\n\tpublic function findRecursive(string $pattern = '.*', bool $sort = false): array {\n\t\tif (!$this->pwd()) {\n\t\t\treturn [];\n\t\t}\n\t\t$startsOn = (string)$this->path;\n\t\t$out = $this->_findRecursive($pattern, $sort);\n\t\t$this->cd($startsOn);\n\n\t\treturn $out;\n\t}\n\n\t/**\n\t * Private helper function for findRecursive.\n\t *\n\t * @param string $pattern Pattern to match against\n\t * @param bool $sort Whether results should be sorted.\n\t * @return array<string> Files matching pattern\n\t */\n\tprotected function _findRecursive(string $pattern, bool $sort = false): array {\n\t\t[$dirs, $files] = $this->read($sort);\n\t\t$found = [];\n\n\t\tforeach ($files as $file) {\n\t\t\tif (preg_match('/^' . $pattern . '$/i', $file)) {\n\t\t\t\t$found[] = static::addPathElement((string)$this->path, $file);\n\t\t\t}\n\t\t}\n\t\t$start = (string)$this->path;\n\n\t\tforeach ($dirs as $dir) {\n\t\t\t$this->cd(static::addPathElement($start, $dir));\n\t\t\t$found = array_merge($found, $this->findRecursive($pattern, $sort));\n\t\t}\n\n\t\treturn $found;\n\t}\n\n\t/**\n\t * Returns true if given $path is a Windows path.\n\t *\n\t * @param string $path Path to check\n\t * @return bool true if windows path, false otherwise\n\t */\n\tpublic static function isWindowsPath(string $path): bool {\n\t\treturn preg_match('/^[A-Z]:\\\\\\\\/i', $path) || str_starts_with($path, '\\\\\\\\');\n\t}\n\n\t/**\n\t * Returns true if given $path is an absolute path.\n\t *\n\t * @param string $path Path to check\n\t * @return bool true if path is absolute.\n\t */\n\tpublic static function isAbsolute(string $path): bool {\n\t\tif (!$path) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn $path[0] === '/' ||\n\t\t\tpreg_match('/^[A-Z]:\\\\\\\\/i', $path) ||\n\t\t\tstr_starts_with($path, '\\\\\\\\') ||\n\t\t\tstatic::isRegisteredStreamWrapper($path);\n\t}\n\n\t/**\n\t * Returns true if given $path is a registered stream wrapper.\n\t *\n\t * @param string $path Path to check\n\t * @return bool True if path is registered stream wrapper.\n\t */\n\tpublic static function isRegisteredStreamWrapper(string $path): bool {\n\t\treturn preg_match('/^[^:\\/]+?(?=:\\/\\/)/', $path, $matches) &&\n\t\t\tin_array($matches[0], stream_get_wrappers(), true);\n\t}\n\n\t/**\n\t * Returns a correct set of slashes for given $path. (\\\\ for Windows paths and / for other paths.)\n\t *\n\t * @param string $path Path to transform\n\t * @return string Path with the correct set of slashes (\"\\\\\" or \"/\")\n\t */\n\tpublic static function normalizeFullPath(string $path): string {\n\t\t$to = static::correctSlashFor($path);\n\t\t$from = ($to === '/' ? '\\\\' : '/');\n\n\t\treturn str_replace($from, $to, $path);\n\t}\n\n\t/**\n\t * Returns a correct set of slashes for given $path. (\\\\ for Windows paths and / for other paths.)\n\t *\n\t * @param string $path Path to check\n\t * @return string Set of slashes (\"\\\\\" or \"/\")\n\t */\n\tpublic static function correctSlashFor(string $path): string {\n\t\treturn static::isWindowsPath($path) ? '\\\\' : '/';\n\t}\n\n\t/**\n\t * Returns $path with added terminating slash (corrected for Windows or other OS).\n\t *\n\t * @param string $path Path to check\n\t * @return string Path with ending slash\n\t */\n\tpublic static function slashTerm(string $path): string {\n\t\tif (static::isSlashTerm($path)) {\n\t\t\treturn $path;\n\t\t}\n\n\t\treturn $path . static::correctSlashFor($path);\n\t}\n\n\t/**\n\t * Returns $path with $element added, with correct slash in-between.\n\t *\n\t * @param string $path Path\n\t * @param array<string>|string $element Element to add at end of path\n\t * @return string Combined path\n\t */\n\tpublic static function addPathElement(string $path, $element): string {\n\t\t$element = (array)$element;\n\t\tarray_unshift($element, rtrim($path, DIRECTORY_SEPARATOR));\n\n\t\treturn implode(DIRECTORY_SEPARATOR, $element);\n\t}\n\n\t/**\n\t * Returns true if the Folder is in the given path.\n\t *\n\t * @param string $path The absolute path to check that the current `pwd()` resides within.\n\t * @param bool $reverse Reverse the search, check if the given `$path` resides within the current `pwd()`.\n\t * @throws \\InvalidArgumentException When the given `$path` argument is not an absolute path.\n\t * @return bool\n\t */\n\tpublic function inPath(string $path, bool $reverse = false): bool {\n\t\tif (!static::isAbsolute($path)) {\n\t\t\tthrow new InvalidArgumentException('The $path argument is expected to be an absolute path.');\n\t\t}\n\n\t\t$dir = static::slashTerm($path);\n\t\t$current = static::slashTerm((string)$this->pwd());\n\n\t\tif (!$reverse) {\n\t\t\t$return = preg_match('/^' . preg_quote($dir, '/') . '(.*)/', $current);\n\t\t} else {\n\t\t\t$return = preg_match('/^' . preg_quote($current, '/') . '(.*)/', $dir);\n\t\t}\n\n\t\treturn (bool)$return;\n\t}\n\n\t/**\n\t * Change the mode on a directory structure recursively. This includes changing the mode on files as well.\n\t *\n\t * @param string $path The path to chmod.\n\t * @param int|null $mode Octal value, e.g. 0755.\n\t * @param bool $recursive Chmod recursively, set to false to only change the current directory.\n\t * @param array<string> $exceptions Array of files, directories to skip.\n\t * @return bool Success.\n\t */\n\tpublic function chmod(string $path, ?int $mode = null, bool $recursive = true, array $exceptions = []): bool {\n\t\tif (!$mode) {\n\t\t\t$mode = $this->mode;\n\t\t}\n\n\t\tif ($recursive === false && is_dir($path)) {\n\t\t\t// phpcs:disable\n\t\t\tif (@chmod($path, intval($mode, 8))) {\n\t\t\t\t// phpcs:enable\n\t\t\t\t$this->_messages[] = sprintf('%s changed to %s', $path, $mode);\n\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t$this->_errors[] = sprintf('%s NOT changed to %s', $path, $mode);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tif (is_dir($path)) {\n\t\t\t$paths = $this->tree($path);\n\n\t\t\tforeach ($paths as $type) {\n\t\t\t\tforeach ($type as $fullpath) {\n\t\t\t\t\t$check = explode(DIRECTORY_SEPARATOR, $fullpath);\n\t\t\t\t\t$count = count($check);\n\n\t\t\t\t\tif (in_array($check[$count - 1], $exceptions, true)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// phpcs:disable\n\t\t\t\t\tif (@chmod($fullpath, intval($mode, 8))) {\n\t\t\t\t\t\t// phpcs:enable\n\t\t\t\t\t\t$this->_messages[] = sprintf('%s changed to %s', $fullpath, $mode);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$this->_errors[] = sprintf('%s NOT changed to %s', $fullpath, $mode);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!$this->_errors) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Returns an array of subdirectories for the provided or current path.\n\t *\n\t * @param string|null $path The directory path to get subdirectories for.\n\t * @param bool $fullPath Whether to return the full path or only the directory name.\n\t * @return array<string> Array of subdirectories for the provided or current path.\n\t */\n\tpublic function subdirectories(?string $path = null, bool $fullPath = true): array {\n\t\tif (!$path) {\n\t\t\t$path = (string)$this->path;\n\t\t}\n\t\t$subdirectories = [];\n\n\t\ttry {\n\t\t\t$iterator = new DirectoryIterator($path);\n\t\t} catch (Exception $e) {\n\t\t\treturn [];\n\t\t}\n\n\t\t/** @var \\DirectoryIterator<\\SplFileInfo> $item */\n\t\tforeach ($iterator as $item) {\n\t\t\tif (!$item->isDir() || $item->isDot()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$subdirectories[] = $fullPath ? $item->getRealPath() : $item->getFilename();\n\t\t}\n\n\t\treturn $subdirectories;\n\t}\n\n\t/**\n\t * Returns an array of nested directories and files in each directory\n\t *\n\t * @param string|null $path the directory path to build the tree from\n\t * @param array<string>|bool $exceptions Either an array of files/folder to exclude\n\t *   or boolean true to not grab dot files/folders\n\t * @param string|null $type either 'file' or 'dir'. Null returns both files and directories\n\t * @return array<mixed> Array of nested directories and files in each directory\n\t */\n\tpublic function tree(?string $path = null, $exceptions = false, ?string $type = null): array {\n\t\tif (!$path) {\n\t\t\t$path = (string)$this->path;\n\t\t}\n\t\t$files = [];\n\t\t$directories = [$path];\n\n\t\tif (is_array($exceptions)) {\n\t\t\t$exceptions = array_flip($exceptions);\n\t\t}\n\t\t$skipHidden = false;\n\t\tif ($exceptions === true) {\n\t\t\t$skipHidden = true;\n\t\t} elseif (isset($exceptions['.'])) {\n\t\t\t$skipHidden = true;\n\t\t\tunset($exceptions['.']);\n\t\t}\n\n\t\t$directory = $iterator = null;\n\t\ttry {\n\t\t\t$directory = new RecursiveDirectoryIterator(\n\t\t\t\t$path,\n\t\t\t\tRecursiveDirectoryIterator::KEY_AS_PATHNAME | RecursiveDirectoryIterator::CURRENT_AS_SELF,\n\t\t\t);\n\t\t\t$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);\n\t\t} catch (Exception $e) {\n\t\t\tunset($directory, $iterator);\n\n\t\t\tif ($type === null) {\n\t\t\t\treturn [[], []];\n\t\t\t}\n\n\t\t\treturn [];\n\t\t}\n\n\t\t/**\n\t\t * @var string $itemPath\n\t\t * @var \\RecursiveDirectoryIterator $fsIterator\n\t\t */\n\t\tforeach ($iterator as $itemPath => $fsIterator) {\n\t\t\tif ($skipHidden) {\n\t\t\t\t$subPathName = $fsIterator->getSubPathname();\n\t\t\t\tif ($subPathName[0] === '.' || str_contains($subPathName, DIRECTORY_SEPARATOR . '.')) {\n\t\t\t\t\tunset($fsIterator);\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\t/** @var \\FilesystemIterator $item */\n\t\t\t$item = $fsIterator->current();\n\t\t\tif (!empty($exceptions) && isset($exceptions[$item->getFilename()])) {\n\t\t\t\tunset($fsIterator, $item);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($item->isFile()) {\n\t\t\t\t$files[] = $itemPath;\n\t\t\t} elseif ($item->isDir() && !$item->isDot()) {\n\t\t\t\t$directories[] = $itemPath;\n\t\t\t}\n\n\t\t\t// inner iterators need to be unset too in order for locks on parents to be released\n\t\t\tunset($fsIterator, $item);\n\t\t}\n\n\t\t// unsetting iterators helps releasing possible locks in certain environments,\n\t\t// which could otherwise make `rmdir()` fail\n\t\tunset($directory, $iterator);\n\n\t\tif ($type === null) {\n\t\t\treturn [$directories, $files];\n\t\t}\n\t\tif ($type === 'dir') {\n\t\t\treturn $directories;\n\t\t}\n\n\t\treturn $files;\n\t}\n\n\t/**\n\t * Create a directory structure recursively.\n\t *\n\t * Can be used to create deep path structures like `/foo/bar/baz/shoe/horn`\n\t *\n\t * @param string $pathname The directory structure to create. Either an absolute or relative\n\t *   path. If the path is relative and exists in the process' cwd it will not be created.\n\t *   Otherwise, relative paths will be prefixed with the current pwd().\n\t * @param int|null $mode octal value 0755\n\t * @return bool Returns TRUE on success, FALSE on failure\n\t */\n\tpublic function create(string $pathname, ?int $mode = null): bool {\n\t\tif (is_dir($pathname) || empty($pathname)) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (!static::isAbsolute($pathname)) {\n\t\t\t$pathname = static::addPathElement((string)$this->pwd(), $pathname);\n\t\t}\n\n\t\tif (!$mode) {\n\t\t\t$mode = $this->mode;\n\t\t}\n\n\t\tif (is_file($pathname)) {\n\t\t\t$this->_errors[] = sprintf('%s is a file', $pathname);\n\n\t\t\treturn false;\n\t\t}\n\t\t$pathname = rtrim($pathname, DIRECTORY_SEPARATOR);\n\t\t$nextPathname = substr($pathname, 0, (int)strrpos($pathname, DIRECTORY_SEPARATOR));\n\n\t\tif ($this->create($nextPathname, $mode)) {\n\t\t\tif (!file_exists($pathname)) {\n\t\t\t\t$old = umask(0);\n\t\t\t\tif (mkdir($pathname, $mode, true)) {\n\t\t\t\t\t$this->_messages[] = sprintf('%s created', $pathname);\n\t\t\t\t\tumask($old);\n\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\t$this->_errors[] = sprintf('%s NOT created', $pathname);\n\t\t\t\tumask($old);\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Returns the size in bytes of this Folder and its contents.\n\t *\n\t * @return int size in bytes of current folder\n\t */\n\tpublic function dirsize(): int {\n\t\t$size = 0;\n\t\t$directory = static::slashTerm((string)$this->path);\n\t\t$stack = [$directory];\n\t\t$count = count($stack);\n\t\tfor ($i = 0, $j = $count; $i < $j; $i++) {\n\t\t\tif (is_file($stack[$i])) {\n\t\t\t\t$size += filesize($stack[$i]);\n\t\t\t} elseif (is_dir($stack[$i])) {\n\t\t\t\t$dir = dir($stack[$i]);\n\t\t\t\tif ($dir) {\n\t\t\t\t\twhile (($entry = $dir->read()) !== false) {\n\t\t\t\t\t\tif ($entry === '.' || $entry === '..') {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$add = $stack[$i] . $entry;\n\n\t\t\t\t\t\tif (is_dir($stack[$i] . $entry)) {\n\t\t\t\t\t\t\t$add = static::slashTerm($add);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$stack[] = $add;\n\t\t\t\t\t}\n\t\t\t\t\t$dir->close();\n\t\t\t\t}\n\t\t\t}\n\t\t\t$j = count($stack);\n\t\t}\n\n\t\treturn $size;\n\t}\n\n\t/**\n\t * Recursively Remove directories if the system allows.\n\t *\n\t * @param string|null $path Path of directory to delete\n\t * @return bool Success\n\t */\n\tpublic function delete(?string $path = null): bool {\n\t\tif (!$path) {\n\t\t\t$path = $this->pwd();\n\t\t}\n\t\tif (!$path) {\n\t\t\treturn false;\n\t\t}\n\t\t$path = static::slashTerm($path);\n\t\tif (is_dir($path)) {\n\t\t\t$directory = $iterator = null;\n\t\t\ttry {\n\t\t\t\t$directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::CURRENT_AS_SELF);\n\t\t\t\t$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::CHILD_FIRST);\n\t\t\t} catch (Exception $e) {\n\t\t\t\tunset($directory, $iterator);\n\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tforeach ($iterator as $item) {\n\t\t\t\t$filePath = $item->getPathname();\n\t\t\t\tif ($item->isFile() || $item->isLink()) {\n\t\t\t\t\t// phpcs:disable\n\t\t\t\t\tif (@unlink($filePath)) {\n\t\t\t\t\t\t// phpcs:enable\n\t\t\t\t\t\t$this->_messages[] = sprintf('%s removed', $filePath);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$this->_errors[] = sprintf('%s NOT removed', $filePath);\n\t\t\t\t\t}\n\t\t\t\t} elseif ($item->isDir() && !$item->isDot()) {\n\t\t\t\t\t// phpcs:disable\n\t\t\t\t\tif (@rmdir($filePath)) {\n\t\t\t\t\t\t// phpcs:enable\n\t\t\t\t\t\t$this->_messages[] = sprintf('%s removed', $filePath);\n\t\t\t\t\t} else {\n\t\t\t\t\t\t$this->_errors[] = sprintf('%s NOT removed', $filePath);\n\n\t\t\t\t\t\tunset($directory, $iterator, $item);\n\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// inner iterators need to be unset too in order for locks on parents to be released\n\t\t\t\tunset($item);\n\t\t\t}\n\n\t\t\t// unsetting iterators helps releasing possible locks in certain environments,\n\t\t\t// which could otherwise make `rmdir()` fail\n\t\t\tunset($directory, $iterator);\n\n\t\t\t$path = rtrim($path, DIRECTORY_SEPARATOR);\n\t\t\t// phpcs:disable\n\t\t\tif (@rmdir($path)) {\n\t\t\t\t// phpcs:enable\n\t\t\t\t$this->_messages[] = sprintf('%s removed', $path);\n\t\t\t} else {\n\t\t\t\t$this->_errors[] = sprintf('%s NOT removed', $path);\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Recursive directory copy.\n\t *\n\t * ### Options\n\t *\n\t * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().\n\t * - `mode` The mode to copy the files/directories with as integer, e.g. 0775.\n\t * - `skip` Files/directories to skip.\n\t * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP\n\t * - `recursive` Whether to copy recursively or not (default: true - recursive)\n\t *\n\t * @param string $to The directory to copy to.\n\t * @param array<string, mixed> $options Array of options (see above).\n\t * @return bool Success.\n\t */\n\tpublic function copy(string $to, array $options = []): bool {\n\t\tif (!$this->pwd()) {\n\t\t\treturn false;\n\t\t}\n\t\t$options += [\n\t\t\t'from' => $this->path,\n\t\t\t'mode' => $this->mode,\n\t\t\t'skip' => [],\n\t\t\t'scheme' => static::MERGE,\n\t\t\t'recursive' => true,\n\t\t];\n\n\t\t$fromDir = $options['from'];\n\t\t$toDir = $to;\n\t\t$mode = $options['mode'];\n\n\t\tif (!$this->cd($fromDir)) {\n\t\t\t$this->_errors[] = sprintf('%s not found', $fromDir);\n\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!is_dir($toDir)) {\n\t\t\t$this->create($toDir, $mode);\n\t\t}\n\n\t\tif (!is_writable($toDir)) {\n\t\t\t$this->_errors[] = sprintf('%s not writable', $toDir);\n\n\t\t\treturn false;\n\t\t}\n\n\t\t$exceptions = array_merge(['.', '..', '.svn'], $options['skip']);\n\t\t// phpcs:disable\n\t\tif ($handle = @opendir($fromDir)) {\n\t\t\t// phpcs:enable\n\t\t\twhile (($item = readdir($handle)) !== false) {\n\t\t\t\t$to = static::addPathElement($toDir, $item);\n\t\t\t\tif (($options['scheme'] !== static::SKIP || !is_dir($to)) && !in_array($item, $exceptions, true)) {\n\t\t\t\t\t$from = static::addPathElement($fromDir, $item);\n\t\t\t\t\tif (is_file($from) && (!is_file($to) || $options['scheme'] !== static::SKIP)) {\n\t\t\t\t\t\tif (copy($from, $to)) {\n\t\t\t\t\t\t\tchmod($to, intval($mode, 8));\n\t\t\t\t\t\t\ttouch($to, filemtime($from) ?: null);\n\t\t\t\t\t\t\t$this->_messages[] = sprintf('%s copied to %s', $from, $to);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$this->_errors[] = sprintf('%s NOT copied to %s', $from, $to);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (is_dir($from) && file_exists($to) && $options['scheme'] === static::OVERWRITE) {\n\t\t\t\t\t\t$this->delete($to);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (is_dir($from) && $options['recursive'] === false) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (is_dir($from) && !file_exists($to)) {\n\t\t\t\t\t\t$old = umask(0);\n\t\t\t\t\t\tif (mkdir($to, $mode, true)) {\n\t\t\t\t\t\t\tumask($old);\n\t\t\t\t\t\t\t$old = umask(0);\n\t\t\t\t\t\t\tchmod($to, $mode);\n\t\t\t\t\t\t\tumask($old);\n\t\t\t\t\t\t\t$this->_messages[] = sprintf('%s created', $to);\n\t\t\t\t\t\t\t$options = ['from' => $from] + $options;\n\t\t\t\t\t\t\t$this->copy($to, $options);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$this->_errors[] = sprintf('%s not created', $to);\n\t\t\t\t\t\t}\n\t\t\t\t\t} elseif (is_dir($from) && $options['scheme'] === static::MERGE) {\n\t\t\t\t\t\t$options = ['from' => $from] + $options;\n\t\t\t\t\t\t$this->copy($to, $options);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tclosedir($handle);\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn empty($this->_errors);\n\t}\n\n\t/**\n\t * Recursive directory move.\n\t *\n\t * ### Options\n\t *\n\t * - `from` The directory to copy from, this will cause a cd() to occur, changing the results of pwd().\n\t * - `mode` The mode to copy the files/directories with as integer, e.g. 0775.\n\t * - `skip` Files/directories to skip.\n\t * - `scheme` Folder::MERGE, Folder::OVERWRITE, Folder::SKIP\n\t * - `recursive` Whether to copy recursively or not (default: true - recursive)\n\t *\n\t * @param string $to The directory to move to.\n\t * @param array<string, mixed> $options Array of options (see above).\n\t * @return bool Success\n\t */\n\tpublic function move(string $to, array $options = []): bool {\n\t\t$options += ['from' => $this->path, 'mode' => $this->mode, 'skip' => [], 'recursive' => true];\n\n\t\tif ($this->copy($to, $options) && $this->delete($options['from'])) {\n\t\t\treturn (bool)$this->cd($to);\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * get messages from latest method\n\t *\n\t * @param bool $reset Reset message stack after reading\n\t * @return array<string>\n\t */\n\tpublic function messages(bool $reset = true): array {\n\t\t$messages = $this->_messages;\n\t\tif ($reset) {\n\t\t\t$this->_messages = [];\n\t\t}\n\n\t\treturn $messages;\n\t}\n\n\t/**\n\t * get error from latest method\n\t *\n\t * @param bool $reset Reset error stack after reading\n\t * @return array<string>\n\t */\n\tpublic function errors(bool $reset = true): array {\n\t\t$errors = $this->_errors;\n\t\tif ($reset) {\n\t\t\t$this->_errors = [];\n\t\t}\n\n\t\treturn $errors;\n\t}\n\n\t/**\n\t * Get the real path (taking \"..\" and such into account)\n\t *\n\t * @param string $path Path to resolve\n\t * @return string|false The resolved path\n\t */\n\tpublic function realpath($path) {\n\t\tif (!str_contains($path, '..')) {\n\t\t\tif (!static::isAbsolute($path)) {\n\t\t\t\t$path = static::addPathElement((string)$this->path, $path);\n\t\t\t}\n\n\t\t\treturn $path;\n\t\t}\n\t\t$path = str_replace('/', DIRECTORY_SEPARATOR, trim($path));\n\t\t$parts = explode(DIRECTORY_SEPARATOR, $path);\n\t\t$newparts = [];\n\t\t$newpath = '';\n\t\tif ($path[0] === DIRECTORY_SEPARATOR) {\n\t\t\t$newpath = DIRECTORY_SEPARATOR;\n\t\t}\n\n\t\twhile (($part = array_shift($parts)) !== null) {\n\t\t\tif ($part === '.' || $part === '') {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ($part === '..') {\n\t\t\t\tif (!empty($newparts)) {\n\t\t\t\t\tarray_pop($newparts);\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t$newparts[] = $part;\n\t\t}\n\t\t$newpath .= implode(DIRECTORY_SEPARATOR, $newparts);\n\n\t\treturn static::slashTerm($newpath);\n\t}\n\n\t/**\n\t * Returns true if given $path ends in a slash (i.e. is slash-terminated).\n\t *\n\t * @param string $path Path to check\n\t * @return bool true if path ends with slash, false otherwise\n\t */\n\tpublic static function isSlashTerm(string $path): bool {\n\t\t$lastChar = $path[strlen($path) - 1];\n\n\t\treturn $lastChar === '/' || $lastChar === '\\\\';\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Directive/BaseDirective.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Directive;\n\nuse IdeHelper\\ValueObject\\KeyValue;\nuse IdeHelper\\ValueObject\\ValueObjectInterface;\n\n/**\n * @see https://blog.jetbrains.com/phpstorm/2019/02/new-phpstorm-meta-php-features/\n * @method array<mixed> toArray()\n */\nabstract class BaseDirective {\n\n\t/**\n\t * Key for sorting inside collection.\n\t *\n\t * @return string\n\t */\n\tabstract public function key();\n\n\t/**\n\t * Final PHP pseudo code.\n\t *\n\t * @return string\n\t */\n\tabstract public function build();\n\n\t/**\n\t * @param array<string|\\IdeHelper\\ValueObject\\ValueObjectInterface> $array\n\t * @param int $indentation\n\t *\n\t * @return string\n\t */\n\tprotected function buildList(array $array, int $indentation = 2): string {\n\t\t$result = [];\n\t\tforeach ($array as $value) {\n\t\t\tif ($value instanceof ValueObjectInterface) {\n\t\t\t\t$element = (string)$value;\n\t\t\t} else {\n\t\t\t\t$element = $value;\n\t\t\t}\n\t\t\t$result[] = str_repeat(\"\\t\", $indentation) . $element;\n\t\t}\n\n\t\t$string = implode(',' . PHP_EOL, $result);\n\t\tif ($string) {\n\t\t\t$string .= ',';\n\t\t}\n\n\t\treturn $string;\n\t}\n\n\t/**\n\t * @param array<string, string|\\IdeHelper\\ValueObject\\ValueObjectInterface> $array\n\t * @param int $indentation\n\t *\n\t * @return string\n\t */\n\tprotected function buildKeyValueMap(array $array, int $indentation = 3): string {\n\t\t$result = [];\n\t\tforeach ($array as $alias => $value) {\n\t\t\tif ($value instanceof KeyValue) {\n\t\t\t\t$key = $value->key();\n\t\t\t\t$value = $value->value();\n\t\t\t} else {\n\t\t\t\t$key = \"'\" . str_replace(\"'\", \"\\'\", $alias) . \"'\";\n\t\t\t}\n\t\t\t$result[] = str_repeat(\"\\t\", $indentation) . $key . ' => ' . $value . ',';\n\t\t}\n\n\t\treturn implode(PHP_EOL, $result);\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Directive/ExitPoint.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Directive;\n\n/**\n * Helps to annotate expected exit point methods.\n *\n * This is available since PhpStorm 2020.01\n *\n * ### Example\n *\n * exitPoint(\\Cake\\Console\\ConsoleIo::abort());\n *\n * @see https://www.jetbrains.com/help/phpstorm/ide-advanced-metadata.html#define-exit-points\n */\nclass ExitPoint extends BaseDirective {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const NAME = 'exitPoint';\n\n\tprotected string $method;\n\n\t/**\n\t * @param string $method\n\t */\n\tpublic function __construct($method) {\n\t\t$this->method = $method;\n\t}\n\n\t/**\n\t * Key for sorting inside collection.\n\t *\n\t * @return string\n\t */\n\tpublic function key() {\n\t\treturn $this->method . '@' . static::NAME;\n\t}\n\n\t/**\n\t * @return array<string, mixed>\n\t */\n\tpublic function toArray() {\n\t\treturn [\n\t\t\t'method' => $this->method,\n\t\t];\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build() {\n\t\t$method = $this->method;\n\n\t\t$result = <<<TXT\n\texitPoint($method);\nTXT;\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Directive/ExpectedArguments.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Directive;\n\n/**\n * Helps to annotate expected method arguments.\n *\n * The position is 0-based.\n *\n * ### Example\n *\n * expectedArguments(\n *     \\MyClass::addArgument(),\n *     1,\n *     \\MyClass::OPTIONAL,\n *     \\MyClass::REQUIRED\n * );\n *\n * or\n *\n * expectedArguments(\n *     \\MyClass::getFlags(),\n *     0,\n *     argumentsSet('myFileObjectFlags')\n * );\n *\n * @see https://www.jetbrains.com/help/phpstorm/ide-advanced-metadata.html#expected-arguments\n */\nclass ExpectedArguments extends BaseDirective {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const NAME = 'expectedArguments';\n\n\tprotected string $method;\n\n\tprotected int $position;\n\n\t/**\n\t * @var array<string|\\IdeHelper\\ValueObject\\ValueObjectInterface>\n\t */\n\tprotected array $list;\n\n\t/**\n\t * @param string $method\n\t * @param int $position Position, 0-based.\n\t * @param array<string|\\IdeHelper\\ValueObject\\ValueObjectInterface> $list\n\t */\n\tpublic function __construct($method, $position, array $list) {\n\t\t$this->method = $method;\n\t\t$this->position = $position;\n\t\t$this->list = $list;\n\t}\n\n\t/**\n\t * Key for sorting inside collection.\n\t *\n\t * @return string\n\t */\n\tpublic function key() {\n\t\treturn $this->method . '@' . $this->position . '@' . static::NAME;\n\t}\n\n\t/**\n\t * @return array<string, mixed>\n\t */\n\tpublic function toArray() {\n\t\treturn [\n\t\t\t'method' => $this->method,\n\t\t\t'position' => $this->position,\n\t\t\t'list' => $this->list,\n\t\t];\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build() {\n\t\t$method = $this->method;\n\t\t$position = $this->position;\n\n\t\t$list = $this->buildList($this->list);\n\t\t$result = <<<TXT\n\texpectedArguments(\n\t\t$method,\n\t\t$position,\n$list\n\t);\nTXT;\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Directive/ExpectedReturnValues.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Directive;\n\n/**\n * Helps to annotate expected method return values.\n *\n * ### Example\n *\n * expectedReturnValues(\n *     \\MyClass::addArgument(),\n *     \\MyClass::SUCCESS,\n *     \\MyClass::ERROR\n * );\n *\n * or\n *\n * expectedReturnValues(\n *     \\MyClass::getFlags(),\n *     argumentsSet('myFileObjectFlags')\n * );\n *\n * @see https://www.jetbrains.com/help/phpstorm/ide-advanced-metadata.html#expected-return-values\n */\nclass ExpectedReturnValues extends BaseDirective {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const NAME = 'expectedReturnValues';\n\n\tprotected string $method;\n\n\t/**\n\t * @var array<string|\\IdeHelper\\ValueObject\\ValueObjectInterface>\n\t */\n\tprotected array $list;\n\n\t/**\n\t * @param string $method\n\t * @param array<string|\\IdeHelper\\ValueObject\\ValueObjectInterface> $list\n\t */\n\tpublic function __construct($method, array $list) {\n\t\t$this->method = $method;\n\t\t$this->list = $list;\n\t}\n\n\t/**\n\t * Key for sorting inside collection.\n\t *\n\t * @return string\n\t */\n\tpublic function key() {\n\t\treturn $this->method . '@' . static::NAME;\n\t}\n\n\t/**\n\t * @return array<string, mixed>\n\t */\n\tpublic function toArray() {\n\t\treturn [\n\t\t\t'method' => $this->method,\n\t\t\t'list' => $this->list,\n\t\t];\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build() {\n\t\t$method = $this->method;\n\t\t$list = $this->buildList($this->list);\n\n\t\t$result = <<<TXT\n\texpectedReturnValues(\n\t\t$method,\n$list\n\t);\nTXT;\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Directive/Override.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Directive;\n\n/**\n * Helps to annotate expected method argument and return type combinations.\n *\n * ### Example\n *\n * override(\n *     \\MyClass::addArgument(0),\n *     map([,\n *         'A' => \\MyClass::class,\n *         '' => '@|\\Iterator',\n *     ])\n * );\n *\n * @see https://www.jetbrains.com/help/phpstorm/ide-advanced-metadata.html#override\n */\nclass Override extends BaseDirective {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const NAME = 'override';\n\n\tprotected string $method;\n\n\t/**\n\t * @var array<string, string|\\IdeHelper\\ValueObject\\ValueObjectInterface>\n\t */\n\tprotected array $map;\n\n\t/**\n\t * @param string $method\n\t * @param array<string, string|\\IdeHelper\\ValueObject\\ValueObjectInterface> $map\n\t */\n\tpublic function __construct($method, array $map) {\n\t\t$this->method = $method;\n\t\t$this->map = $map;\n\t}\n\n\t/**\n\t * @return array<string, mixed>\n\t */\n\tpublic function toArray() {\n\t\treturn [\n\t\t\t'method' => $this->method,\n\t\t\t'map' => $this->map,\n\t\t];\n\t}\n\n\t/**\n\t * Key for sorting inside collection.\n\t *\n\t * @return string\n\t */\n\tpublic function key() {\n\t\treturn $this->method . '@' . static::NAME;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build() {\n\t\t$method = $this->method;\n\t\t$mapDefinitions = $this->buildKeyValueMap($this->map);\n\n\t\t$result = <<<TXT\n\toverride(\n\t\t$method,\n\t\tmap([\n$mapDefinitions\n\t\t]),\n\t);\nTXT;\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Directive/RegisterArgumentsSet.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Directive;\n\n/**\n * Helps to register an argument set to be used in other directives for DRY code.\n *\n * ### Example\n *\n * registerArgumentsSet(\n *     'mySet',\n *     \\MyClass::OPTIONAL,\n *     \\MyClass::REQUIRED\n * );\n *\n * Then it can be used in other places as argumentsSet('mySet').\n *\n * @see https://www.jetbrains.com/help/phpstorm/ide-advanced-metadata.html#arguments-set\n */\nclass RegisterArgumentsSet extends BaseDirective {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const NAME = 'registerArgumentsSet';\n\n\tprotected string $set;\n\n\t/**\n\t * @var array<string|\\IdeHelper\\ValueObject\\ValueObjectInterface>\n\t */\n\tprotected array $list;\n\n\t/**\n\t * @param string $set\n\t * @param array<string|\\IdeHelper\\ValueObject\\ValueObjectInterface> $list\n\t */\n\tpublic function __construct($set, array $list) {\n\t\t$this->set = $set;\n\t\t$this->list = $list;\n\t}\n\n\t/**\n\t * Key for sorting inside collection.\n\t *\n\t * @return string\n\t */\n\tpublic function key() {\n\t\treturn $this->set . '@' . static::NAME;\n\t}\n\n\t/**\n\t * @return array<string, mixed>\n\t */\n\tpublic function toArray() {\n\t\treturn [\n\t\t\t'set' => $this->set,\n\t\t\t'list' => $this->list,\n\t\t];\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function build() {\n\t\t$set = \"'\" . $this->set . \"'\";\n\t\t$list = $this->buildList($this->list);\n\n\t\t$result = <<<TXT\n\tregisterArgumentsSet(\n\t\t$set,\n$list\n\t);\nTXT;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function __toString() {\n\t\treturn 'argumentsSet(\\'' . $this->set . '\\')';\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/GeneratorInterface.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator;\n\ninterface GeneratorInterface {\n\n\t/**\n\t * @return string\n\t */\n\tpublic function generate(): string;\n\n}\n"
  },
  {
    "path": "src/Generator/PhpstormGenerator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator;\n\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\n\nclass PhpstormGenerator implements GeneratorInterface {\n\n\tprotected TaskCollection $taskCollection;\n\n\tprotected ?Io $io = null;\n\n\t/**\n\t * @param \\IdeHelper\\Generator\\TaskCollection $taskCollection\n\t * @param \\IdeHelper\\Console\\Io|null $io\n\t */\n\tpublic function __construct(TaskCollection $taskCollection, ?Io $io = null) {\n\t\t$this->taskCollection = $taskCollection;\n\t\t$this->io = $io;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function generate(): string {\n\t\t$map = $this->taskCollection->getMap();\n\n\t\t$this->outputSetInfo($map);\n\n\t\treturn $this->build($map);\n\t}\n\n\t/**\n\t * @param array<\\IdeHelper\\Generator\\Directive\\BaseDirective> $map\n\t *\n\t * @return string\n\t */\n\tprotected function build(array $map): string {\n\t\t$overrides = [];\n\t\tforeach ($map as $directive) {\n\t\t\t$overrides[] = $directive->build();\n\t\t}\n\t\t$overrides = implode(PHP_EOL . PHP_EOL, $overrides);\n\n\t\t$template = <<<TXT\n<?php\n// @link https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata\nnamespace PHPSTORM_META {\n\n$overrides\n\n}\n\nTXT;\n\n\t\treturn $template;\n\t}\n\n\t/**\n\t * @param array<\\IdeHelper\\Generator\\Directive\\BaseDirective> $map\n\t *\n\t * @return void\n\t */\n\tprotected function outputSetInfo(array $map): void {\n\t\tif (!$this->io) {\n\t\t\treturn;\n\t\t}\n\n\t\t$sets = [];\n\t\tforeach ($map as $directive) {\n\t\t\tif ($directive instanceof RegisterArgumentsSet) {\n\t\t\t\t$sets[] = $directive->toArray()['set'];\n\t\t\t}\n\t\t}\n\n\t\t$this->io->verbose('The following sets are available for re-use:');\n\t\tforeach ($sets as $set) {\n\t\t\t$this->io->verbose('- ' . $set);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/BehaviorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\ORM\\Table;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\ClassName;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass BehaviorTask implements TaskInterface {\n\n\tpublic const CLASS_TABLE = Table::class;\n\n\t/**\n\t * @var array<string, int>\n\t */\n\tprotected array $addAliases = [\n\t\t'\\\\' . self::CLASS_TABLE . '::addBehavior()' => 0,\n\t];\n\n\t/**\n\t * @var array<string, int>\n\t */\n\tprotected array $removeAliases = [\n\t\t'\\\\' . self::CLASS_TABLE . '::removeBehavior()' => 0,\n\t];\n\n\t/**\n\t * @var array<string, int>\n\t */\n\tprotected array $hasAliases = [\n\t\t'\\\\' . self::CLASS_TABLE . '::hasBehavior()' => 0,\n\t];\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $getAliases = [\n\t\t'\\\\' . self::CLASS_TABLE . '::getBehavior()',\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$prefixedList = $nonPrefixedList = [];\n\t\t$behaviors = $this->collectBehaviors();\n\t\tforeach ($behaviors as $name => $className) {\n\t\t\t$prefixedList[$name] = StringName::create($name);\n\t\t\tif (str_contains($name, '.')) {\n\t\t\t\t[, $name] = pluginSplit($name);\n\t\t\t}\n\t\t\t$nonPrefixedList[$name] = StringName::create($name);\n\t\t}\n\n\t\tksort($prefixedList);\n\t\tksort($nonPrefixedList);\n\n\t\t$result = [];\n\t\tforeach ($this->addAliases as $alias => $position) {\n\t\t\t$directive = new ExpectedArguments($alias, $position, $prefixedList);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\t\tforeach ($this->removeAliases as $alias => $position) {\n\t\t\t$directive = new ExpectedArguments($alias, $position, $nonPrefixedList);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\t\tforeach ($this->hasAliases as $alias => $position) {\n\t\t\t$directive = new ExpectedArguments($alias, $position, $nonPrefixedList);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\t\tforeach ($this->getAliases as $alias) {\n\t\t\t$map = [];\n\t\t\tforeach ($behaviors as $name => $className) {\n\t\t\t\tif (str_contains($name, '.')) {\n\t\t\t\t\t[, $name] = pluginSplit($name);\n\t\t\t\t}\n\t\t\t\t$map[$name] = ClassName::create($className);\n\t\t\t}\n\n\t\t\tksort($map);\n\n\t\t\t$directive = new Override($alias, $map);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectBehaviors(): array {\n\t\t$behaviors = [];\n\n\t\t$folders = array_merge(App::core('ORM/Behavior'), AppPath::get('Model/Behavior'));\n\t\tforeach ($folders as $folder) {\n\t\t\t$behaviors = $this->addBehaviors($behaviors, $folder);\n\t\t}\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$folders = AppPath::get('Model/Behavior', $plugin);\n\t\t\tforeach ($folders as $folder) {\n\t\t\t\t$behaviors = $this->addBehaviors($behaviors, $folder, $plugin);\n\t\t\t}\n\t\t}\n\n\t\tksort($behaviors);\n\n\t\treturn $behaviors;\n\t}\n\n\t/**\n\t * @param array<string, string> $behaviors\n\t * @param string $folder\n\t * @param string|null $plugin\n\t *\n\t * @return array<string, string>\n\t */\n\tprotected function addBehaviors(array $behaviors, string $folder, ?string $plugin = null): array {\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\tpreg_match('/^(.+)Behavior\\.php$/', $file, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$fullName = $matches[1];\n\t\t\t$name = $fullName;\n\t\t\tif ($plugin) {\n\t\t\t\t$name = $plugin . '.' . $fullName;\n\t\t\t}\n\n\t\t\t$className = App::className($name, 'Model/Behavior', 'Behavior');\n\t\t\tif (!$className) {\n\t\t\t\t$className = \"Cake\\ORM\\Behavior\\\\{$name}Behavior\";\n\t\t\t}\n\n\t\t\t$behaviors[$name] = $className;\n\t\t}\n\n\t\treturn $behaviors;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/CacheTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Cache\\Cache;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass CacheTask implements TaskInterface {\n\n\tpublic const CLASS_CACHE = Cache::class;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const SET_CACHE_ENGINES = 'cacheEngines';\n\n\t/**\n\t * @var array<int>\n\t */\n\tprotected array $aliases = [\n\t\t'\\\\' . self::CLASS_CACHE . '::clear()' => 0,\n\t\t'\\\\' . self::CLASS_CACHE . '::read()' => 1,\n\t\t'\\\\' . self::CLASS_CACHE . '::readMany()' => 1,\n\t\t'\\\\' . self::CLASS_CACHE . '::delete()' => 1,\n\t\t'\\\\' . self::CLASS_CACHE . '::deleteMany()' => 1,\n\t\t'\\\\' . self::CLASS_CACHE . '::clearGroup()' => 1,\n\t\t'\\\\' . self::CLASS_CACHE . '::add()' => 2,\n\t\t'\\\\' . self::CLASS_CACHE . '::write()' => 2,\n\t\t'\\\\' . self::CLASS_CACHE . '::increment()' => 2,\n\t\t'\\\\' . self::CLASS_CACHE . '::decrement()' => 2,\n\t\t'\\\\' . self::CLASS_CACHE . '::remember()' => 2,\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$list = $this->collectCacheEngines();\n\t\t$registerArgumentsSet = new RegisterArgumentsSet(static::SET_CACHE_ENGINES, $list);\n\t\t$result[$registerArgumentsSet->key()] = $registerArgumentsSet;\n\n\t\tforeach ($this->aliases as $alias => $position) {\n\t\t\t$directive = new ExpectedArguments($alias, $position, [$registerArgumentsSet]);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<\\IdeHelper\\ValueObject\\StringName>\n\t */\n\tprotected function collectCacheEngines(): array {\n\t\t$cacheEngines = Cache::configured();\n\n\t\t$result = [];\n\t\tforeach ($cacheEngines as $cacheEngine) {\n\t\t\t$result[$cacheEngine] = StringName::create($cacheEngine);\n\t\t}\n\n\t\tksort($result);\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/CellTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\View\\Cell;\nuse Cake\\View\\CellTrait;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\ClassName;\n\nclass CellTask implements TaskInterface {\n\n\tpublic const CLASS_CELL = CellTrait::class;\n\n\tprotected static string $alias = '\\\\' . self::CLASS_CELL . '::cell()';\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$map = [];\n\n\t\t$cells = $this->collectCells();\n\t\tforeach ($cells as $name => $className) {\n\t\t\t$map[$name] = ClassName::create($className);\n\t\t}\n\n\t\tksort($map);\n\n\t\t$result = [];\n\t\tif ($map) {\n\t\t\t$directive = new Override(static::$alias, $map);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectCells(): array {\n\t\t$cells = [];\n\n\t\t$folders = AppPath::get('View/Cell');\n\t\tforeach ($folders as $folder) {\n\t\t\t$cells = $this->addCells($cells, $folder);\n\t\t}\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$folders = AppPath::get('View/Cell', $plugin);\n\t\t\tforeach ($folders as $folder) {\n\t\t\t\t$cells = $this->addCells($cells, $folder, $plugin);\n\t\t\t}\n\t\t}\n\n\t\treturn $cells;\n\t}\n\n\t/**\n\t * @param array<string> $components\n\t * @param string $folder\n\t * @param string|null $plugin\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addCells(array $components, $folder, $plugin = null) {\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\tpreg_match('/^(.+)Cell\\.php$/', $file, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$name = $matches[1];\n\t\t\tif ($plugin) {\n\t\t\t\t$name = $plugin . '.' . $name;\n\t\t\t}\n\n\t\t\t$className = App::className($name, 'View/Cell', 'Cell');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$methods = get_class_methods($className);\n\t\t\t$defaultCellMethods = get_class_methods(Cell::class);\n\n\t\t\tforeach ($methods as $method) {\n\t\t\t\tif (!in_array($method, $defaultCellMethods, true)) {\n\t\t\t\t\t$components[$name . '::' . $method] = $className;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tunset($components[$name . '::display']);\n\t\t\t$components[$name] = $className;\n\t\t}\n\n\t\treturn $components;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/ComponentTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Controller\\ComponentRegistry;\nuse Cake\\Controller\\Controller;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\ClassName;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass ComponentTask implements TaskInterface {\n\n\tpublic const CLASS_CONTROLLER = Controller::class;\n\tpublic const CLASS_COMPONENT_REGISTRY = ComponentRegistry::class;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $loadAliases = [\n\t\t'\\\\' . self::CLASS_CONTROLLER . '::loadComponent(0)',\n\t];\n\n\t/**\n\t * @var array<string, int>\n\t */\n\tprotected array $unloadAliases = [\n\t\t'\\\\' . self::CLASS_COMPONENT_REGISTRY . '::unload()' => 0,\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$addMap = [];\n\t\t$removeList = [];\n\n\t\t$components = $this->collectComponents();\n\t\tforeach ($components as $name => $className) {\n\t\t\t$addMap[$name] = ClassName::create($className);\n\t\t\tif (str_contains($name, '.')) {\n\t\t\t\t[, $name] = pluginSplit($name);\n\t\t\t}\n\t\t\t$removeList[$name] = StringName::create($name);\n\t\t}\n\n\t\tksort($addMap);\n\t\tksort($removeList);\n\n\t\t$result = [];\n\t\tforeach ($this->loadAliases as $alias) {\n\t\t\t$directive = new Override($alias, $addMap);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\t\tforeach ($this->unloadAliases as $alias => $position) {\n\t\t\t$directive = new ExpectedArguments($alias, $position, $removeList);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectComponents(): array {\n\t\t$components = [];\n\n\t\t$folders = array_merge(App::core('Controller/Component'), AppPath::get('Controller/Component'));\n\t\tforeach ($folders as $folder) {\n\t\t\t$components = $this->addComponents($components, $folder);\n\t\t}\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$folders = AppPath::get('Controller/Component', $plugin);\n\t\t\tforeach ($folders as $folder) {\n\t\t\t\t$components = $this->addComponents($components, $folder, $plugin);\n\t\t\t}\n\t\t}\n\n\t\treturn $components;\n\t}\n\n\t/**\n\t * @param array<string> $components\n\t * @param string $folder\n\t * @param string|null $plugin\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addComponents(array $components, $folder, $plugin = null) {\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\tpreg_match('/^(.+)Component\\.php$/', $file, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$name = $matches[1];\n\t\t\tif ($plugin) {\n\t\t\t\t$name = $plugin . '.' . $name;\n\t\t\t}\n\n\t\t\t$className = App::className($name, 'Controller/Component', 'Component');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$components[$name] = $className;\n\t\t}\n\n\t\treturn $components;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/ConfigureTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass ConfigureTask implements TaskInterface {\n\n\tpublic const CLASS_CONFIGURE = Configure::class;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const SET_CONFIGURE_KEYS = 'configureKeys';\n\n\t/**\n\t * @var array<int>\n\t */\n\tprotected array $methods = [\n\t\t'\\\\' . self::CLASS_CONFIGURE . '::read()' => 0,\n\t\t'\\\\' . self::CLASS_CONFIGURE . '::readOrFail()' => 0,\n\t\t'\\\\' . self::CLASS_CONFIGURE . '::check()' => 0,\n\t\t'\\\\' . self::CLASS_CONFIGURE . '::write()' => 0,\n\t\t'\\\\' . self::CLASS_CONFIGURE . '::delete()' => 0,\n\t\t'\\\\' . self::CLASS_CONFIGURE . '::consume()' => 0,\n\t\t'\\\\' . self::CLASS_CONFIGURE . '::consumeOrFail()' => 0,\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$list = $this->collectKeys();\n\t\t$registerArgumentsSet = new RegisterArgumentsSet(static::SET_CONFIGURE_KEYS, $list);\n\t\t$result[$registerArgumentsSet->key()] = $registerArgumentsSet;\n\n\t\tforeach ($this->methods as $method => $position) {\n\t\t\t$directive = new ExpectedArguments($method, $position, [$registerArgumentsSet]);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectKeys(): array {\n\t\t$keys = [];\n\n\t\t$configure = (array)Configure::read();\n\t\t$keys = $this->addKeys($keys, $configure);\n\n\t\tksort($keys);\n\n\t\treturn $keys;\n\t}\n\n\t/**\n\t * @param array<string, mixed> $keys\n\t * @param array<mixed> $data\n\t * @param array<string> $path\n\t *\n\t * @return array<string, mixed>\n\t */\n\tprotected function addKeys(array $keys, array $data, array $path = []): array {\n\t\tforeach ($data as $key => $row) {\n\t\t\tif (is_numeric($key)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$subPath = $path;\n\t\t\t$subPath[] = $key;\n\t\t\t$subPathString = implode('.', $subPath);\n\n\t\t\t$keys[$subPathString] = StringName::create($subPathString);\n\n\t\t\tif (!is_array($row)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$keys = $this->addKeys($keys, $row, $subPath);\n\t\t}\n\n\t\treturn $keys;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/ConnectionTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Datasource\\ConnectionManager;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass ConnectionTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tprotected const METHOD_GET = '\\\\' . ConnectionManager::class . '::get()';\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$keys = $this->connectionKeys();\n\n\t\tksort($keys);\n\n\t\t$directive = new ExpectedArguments(static::METHOD_GET, 0, $keys);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<\\IdeHelper\\ValueObject\\StringName>\n\t */\n\tprotected function connectionKeys(): array {\n\t\t$configured = ConnectionManager::configured();\n\n\t\t$list = [];\n\t\tforeach ($configured as $key) {\n\t\t\t$list[$key] = StringName::create($key);\n\t\t}\n\n\t\treturn $list;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/ConsoleHelperTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\ClassName;\n\nclass ConsoleHelperTask implements TaskInterface {\n\n\tpublic const CLASS_CONSOLE_IO = ConsoleIo::class;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $loadAliases = [\n\t\t'\\\\' . self::CLASS_CONSOLE_IO . '::helper(0)',\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$addMap = [];\n\n\t\t$components = $this->collectHelpers();\n\t\tforeach ($components as $name => $className) {\n\t\t\t$addMap[$name] = ClassName::create($className);\n\t\t\tif (str_contains($name, '.')) {\n\t\t\t\t[, $name] = pluginSplit($name);\n\t\t\t}\n\t\t}\n\n\t\tksort($addMap);\n\n\t\t$result = [];\n\t\tforeach ($this->loadAliases as $alias) {\n\t\t\t$directive = new Override($alias, $addMap);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectHelpers(): array {\n\t\t$helpers = [];\n\n\t\t$folders = array_merge(App::core('Command/Helper'), AppPath::get('Command/Helper'));\n\t\tforeach ($folders as $folder) {\n\t\t\t$helpers = $this->addHelpers($helpers, $folder);\n\t\t}\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$folders = AppPath::get('Command/Helper', $plugin);\n\t\t\tforeach ($folders as $folder) {\n\t\t\t\t$helpers = $this->addHelpers($helpers, $folder, $plugin);\n\t\t\t}\n\t\t}\n\n\t\treturn $helpers;\n\t}\n\n\t/**\n\t * @param array<string> $helpers\n\t * @param string $folder\n\t * @param string|null $plugin\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addHelpers(array $helpers, $folder, $plugin = null) {\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\tpreg_match('/^(.+)Helper\\.php$/', $file, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$name = $matches[1];\n\t\t\tif ($plugin) {\n\t\t\t\t$name = $plugin . '.' . $name;\n\t\t\t}\n\n\t\t\t$className = App::className($name, 'Command/Helper', 'Helper');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$helpers[$name] = $className;\n\t\t}\n\n\t\treturn $helpers;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/ConsoleTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Generator\\Directive\\ExitPoint;\n\nclass ConsoleTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tprotected const METHOD_ABORT = '\\\\' . ConsoleIo::class . '::abort()';\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$directive = new ExitPoint(static::METHOD_ABORT);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/DatabaseTableColumnNameTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Datasource\\ConnectionManager;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\ValueObject\\StringName;\n\n/**\n * This task is useful when using Migrations plugin and creating Migration files.\n */\nclass DatabaseTableColumnNameTask extends DatabaseTableTask {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const SET_COLUMN_NAMES = 'columnNames';\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $aliases = [\n\t\t'\\Migrations\\Db\\Table::addColumn()',\n\t\t'\\Migrations\\Db\\Table::changeColumn()',\n\t\t'\\Migrations\\Db\\Table::removeColumn()',\n\t\t'\\Migrations\\Db\\Table::renameColumn()',\n\t\t'\\Migrations\\Db\\Table::hasColumn()',\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$list = [];\n\n\t\t$names = $this->collectTableColumnNames();\n\t\tforeach ($names as $type) {\n\t\t\t$list[$type] = StringName::create($type);\n\t\t}\n\n\t\tksort($list);\n\n\t\t$result = [];\n\t\t$registerArgumentsSet = new RegisterArgumentsSet(static::SET_COLUMN_NAMES, $list);\n\t\t$result[$registerArgumentsSet->key()] = $registerArgumentsSet;\n\n\t\tforeach ($this->aliases as $alias) {\n\t\t\t$directive = new ExpectedArguments($alias, 0, [$registerArgumentsSet]);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\t$directive = new ExpectedArguments('\\Migrations\\Db\\Table::renameColumn()', 1, [$registerArgumentsSet]);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectTableColumnNames(): array {\n\t\t$schema = $this->getConnection()->getSchemaCollection();\n\n\t\t$tables = $this->collectTables();\n\n\t\t$columns = [];\n\t\tforeach ($tables as $table) {\n\t\t\t$tableSchema = $schema->describe($table);\n\t\t\t$columns = array_merge($columns, $tableSchema->columns());\n\t\t}\n\n\t\treturn array_unique($columns);\n\t}\n\n\t/**\n\t * @param string $name\n\t *\n\t * @return \\Cake\\Database\\Connection\n\t */\n\tprotected function getConnection(string $name = 'default') {\n\t\t/** @var \\Cake\\Database\\Connection $connection */\n\t\t$connection = ConnectionManager::get($name);\n\n\t\treturn $connection;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/DatabaseTableColumnTypeTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Core\\Plugin;\nuse Cake\\Datasource\\ConnectionManager;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\ValueObject\\StringName;\nuse Migrations\\Db\\Adapter\\AdapterFactory;\nuse Migrations\\Db\\Adapter\\AdapterInterface;\nuse Migrations\\Migrations;\nuse Phinx\\Db\\Adapter\\AdapterInterface as PhinxAdapterInterface;\n\n/**\n * This task is useful when using Migrations plugin and creating Migration files.\n */\nclass DatabaseTableColumnTypeTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const SET_COLUMN_TYPES = 'columnTypes';\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $aliases = [\n\t\t'\\Migrations\\Db\\Table::addColumn()',\n\t\t'\\Migrations\\Db\\Table::changeColumn()',\n\t];\n\n\t/**\n\t * Used if the Migrations plugin is not loaded\n\t *\n\t * @var array<string>\n\t */\n\tprotected array $defaultTypes = [\n\t\t'string',\n\t\t'char',\n\t\t'text',\n\t\t'integer',\n\t\t'smallinteger',\n\t\t'biginteger',\n\t\t'bit',\n\t\t'float',\n\t\t'decimal',\n\t\t'double',\n\t\t'datetime',\n\t\t'timestamp',\n\t\t'time',\n\t\t'date',\n\t\t'blob',\n\t\t'binary',\n\t\t'boolean',\n\t\t'uuid',\n\t\t'year',\n\t\t'json',\n\t\t'binaryuuid',\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$list = [];\n\n\t\t$types = $this->collectTableColumnTypes();\n\t\tforeach ($types as $type) {\n\t\t\t$list[$type] = StringName::create($type);\n\t\t}\n\n\t\tksort($list);\n\n\t\t$result = [];\n\t\t$registerArgumentsSet = new RegisterArgumentsSet(static::SET_COLUMN_TYPES, $list);\n\t\t$result[$registerArgumentsSet->key()] = $registerArgumentsSet;\n\n\t\tforeach ($this->aliases as $alias) {\n\t\t\t$directive = new ExpectedArguments($alias, 1, [$registerArgumentsSet]);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectTableColumnTypes(): array {\n\t\tif (!Plugin::isLoaded('Migrations')) {\n\t\t\treturn $this->defaultTypes;\n\t\t}\n\n\t\t$adapter = $this->getAdapter();\n\n\t\treturn $adapter->getColumnTypes();\n\t}\n\n\t/**\n\t * @param string $name\n\t *\n\t * @return \\Phinx\\Db\\Adapter\\AdapterInterface|\\Migrations\\Db\\Adapter\\AdapterInterface\n\t */\n\tprotected function getAdapter(string $name = 'default') {\n\t\t// Migrations v5+ (no Phinx)\n\t\tif (class_exists('Migrations\\Db\\Adapter\\AdapterFactory')) {\n\t\t\treturn $this->getAdapterV5($name);\n\t\t}\n\n\t\t// Migrations v4 (Phinx-based)\n\t\treturn $this->getAdapterV4($name);\n\t}\n\n\t/**\n\t * @param string $name\n\t *\n\t * @return \\Migrations\\Db\\Adapter\\AdapterInterface\n\t */\n\tprotected function getAdapterV5(string $name): AdapterInterface {\n\t\t/** @var \\Cake\\Database\\Connection $connection */\n\t\t$connection = ConnectionManager::get($name);\n\t\t$driver = $connection->getDriver();\n\t\t$driverClass = get_class($driver);\n\t\t$driverName = strtolower(substr((string)strrchr($driverClass, '\\\\'), 1));\n\n\t\t$config = $connection->config();\n\t\t$database = $config['database'] ?? null;\n\n\t\t$factory = AdapterFactory::instance();\n\n\t\treturn $factory->getAdapter($driverName, [\n\t\t\t'adapter' => $driverName,\n\t\t\t'connection' => $connection,\n\t\t\t'database' => $database,\n\t\t]);\n\t}\n\n\t/**\n\t * @deprecated Will be removed in a future version\n\t *\n\t * @param string $name\n\t *\n\t * @return \\Phinx\\Db\\Adapter\\AdapterInterface\n\t */\n\tprotected function getAdapterV4(string $name): PhinxAdapterInterface {\n\t\t$params = [\n\t\t\t'connection' => $name,\n\t\t];\n\n\t\t$migrations = new Migrations();\n\t\t$input = $migrations->getInput('Migrate', [], $params);\n\t\t$migrations->setInput($input);\n\t\t$manager = $migrations->getManager($migrations->getConfig());\n\n\t\t$env = $manager->getEnvironment('default');\n\n\t\treturn $env->getAdapter();\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/DatabaseTableTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Bake\\Utility\\TableScanner;\nuse Cake\\Core\\Configure;\nuse Cake\\Datasource\\ConnectionManager;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\ValueObject\\StringName;\nuse Throwable;\n\n/**\n * This task is useful when using Migrations plugin and creating Migration files.\n */\nclass DatabaseTableTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const SET_TABLE_NAMES = 'tableNames';\n\n\t/**\n\t * @var array<string>|null\n\t */\n\tprotected static ?array $tables = null;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $aliases = [\n\t\t'\\Migrations\\BaseMigration::table()',\n\t\t'\\Migrations\\BaseMigration::hasTable()',\n\t\t'\\Migrations\\BaseSeed::table()',\n\t\t'\\Migrations\\BaseSeed::hasTable()',\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$list = [];\n\n\t\t$tables = $this->collectTables();\n\t\tforeach ($tables as $table) {\n\t\t\t$list[$table] = StringName::create($table);\n\t\t}\n\n\t\t$result = [];\n\n\t\tksort($list);\n\t\t$registerArgumentsSet = new RegisterArgumentsSet(static::SET_TABLE_NAMES, $list);\n\t\t$result[$registerArgumentsSet->key()] = $registerArgumentsSet;\n\n\t\tforeach ($this->aliases as $alias) {\n\t\t\t$directive = new ExpectedArguments($alias, 0, [$registerArgumentsSet]);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectTables(): array {\n\t\tif (static::$tables !== null) {\n\t\t\t$tables = static::$tables;\n\t\t} else {\n\t\t\t$db = $this->getConnection();\n\t\t\ttry {\n\t\t\t\t/** @var array<string>|null $ignore */\n\t\t\t\t$ignore = Configure::read('IdeHelper.ignoreDatabaseTables');\n\t\t\t\t$tables = (new TableScanner($db, $ignore))->listUnskipped();\n\t\t\t} catch (Throwable $exception) {\n\t\t\t\t$tables = [];\n\t\t\t}\n\t\t\tstatic::$tables = $tables;\n\t\t}\n\n\t\t$blacklist = (array)Configure::read('IdeHelper.skipDatabaseTables');\n\t\tforeach ($tables as $key => $table) {\n\t\t\tforeach ($blacklist as $regex) {\n\t\t\t\tif (preg_match($regex, $table)) {\n\t\t\t\t\tunset($tables[$key]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn $tables;\n\t}\n\n\t/**\n\t * @param string $name\n\t *\n\t * @return \\Cake\\Database\\Connection\n\t */\n\tprotected function getConnection(string $name = 'default') {\n\t\t/** @var \\Cake\\Database\\Connection $connection */\n\t\t$connection = ConnectionManager::get($name);\n\n\t\treturn $connection;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/DatabaseTypeTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Database\\TypeFactory;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\ValueObject\\ClassName;\nuse IdeHelper\\ValueObject\\StringName;\nuse Throwable;\n\n/**\n * For Database TypeFactory calls around column types.\n */\nclass DatabaseTypeTask implements TaskInterface {\n\n\tpublic const CLASS_TYPE = TypeFactory::class;\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$types = $this->getTypes();\n\n\t\t$map = [];\n\t\tforeach ($types as $type => $className) {\n\t\t\t$map[$type] = ClassName::create($className);\n\t\t}\n\t\tksort($map);\n\n\t\t$method = '\\\\' . static::CLASS_TYPE . '::build(0)';\n\t\t$directive = new Override($method, $map);\n\t\t$result[$directive->key()] = $directive;\n\n\t\t$list = [];\n\t\tforeach ($types as $type => $className) {\n\t\t\t$list[$type] = StringName::create($type);\n\t\t}\n\t\tksort($list);\n\n\t\t$method = '\\\\' . static::CLASS_TYPE . '::map()';\n\t\t$directive = new ExpectedArguments($method, 0, $list);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function getTypes(): array {\n\t\t$types = [];\n\n\t\ttry {\n\t\t\t$allTypes = TypeFactory::buildAll();\n\t\t} catch (Throwable $exception) {\n\t\t\treturn $types;\n\t\t}\n\n\t\tforeach ($allTypes as $key => $type) {\n\t\t\tif (str_starts_with($key, 'enum-')) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$types[$key] = get_class($type);\n\t\t}\n\n\t\tksort($types);\n\n\t\treturn $types;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/ElementTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Core\\App;\nuse Cake\\View\\View;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\StringName;\nuse RecursiveDirectoryIterator;\nuse RecursiveIteratorIterator;\nuse RecursiveRegexIterator;\nuse RegexIterator;\n\nclass ElementTask implements TaskInterface {\n\n\tpublic const CLASS_VIEW = View::class;\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$elements = $this->collectElements();\n\t\t$list = [];\n\t\tforeach ($elements as $element) {\n\t\t\t$list[$element] = StringName::create($element);\n\t\t}\n\n\t\tksort($list);\n\n\t\t$method = '\\\\' . static::CLASS_VIEW . '::element()';\n\t\t$directive = new ExpectedArguments($method, 0, $list);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectElements(): array {\n\t\t$paths = App::path('templates');\n\n\t\t$result = [];\n\t\t$result = $this->addElements($result, $paths);\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$paths = App::path('templates', $plugin);\n\t\t\t$result = $this->addElements($result, $paths, $plugin);\n\t\t}\n\n\t\tsort($result);\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param array<string> $result\n\t * @param array<string> $paths\n\t * @param string|null $plugin\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addElements(array $result, array $paths, ?string $plugin = null): array {\n\t\tforeach ($paths as $path) {\n\t\t\t$path .= 'element' . DS;\n\t\t\tif (!is_dir($path)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$Directory = new RecursiveDirectoryIterator($path);\n\t\t\t$Iterator = new RecursiveIteratorIterator($Directory);\n\t\t\t$Regex = new RegexIterator($Iterator, '/^.+\\.php$/i', RecursiveRegexIterator::GET_MATCH);\n\n\t\t\tforeach ($Regex as $file) {\n\t\t\t\t$name = str_replace($path, '', $file[0]);\n\t\t\t\t$name = substr($name, 0, -4);\n\t\t\t\t$name = str_replace(DS, '/', $name);\n\t\t\t\tif ($plugin) {\n\t\t\t\t\t$name = $plugin . '.' . $name;\n\t\t\t\t}\n\t\t\t\t$result[] = $name;\n\t\t\t}\n\t\t}\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/EntityTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\ORM\\Table;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\ValueObject\\StringName;\nuse Throwable;\n\nclass EntityTask extends ModelTask {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const SET_ENTITY_FIELDS = 'entityFields';\n\n\t/**\n\t * @var array<int> array<string, int>\n\t */\n\tpublic static array $methods = [\n\t\t'has' => 0,\n\t\t'get' => 0,\n\t\t'hasValue' => 0,\n\t\t'isEmpty' => 0,\n\t\t'isDirty' => 0,\n\t\t'getOriginal' => 0,\n\t\t'setDirty' => 0,\n\t\t'setError' => 0,\n\t\t'getError' => 0,\n\t\t'getInvalidField' => 0,\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$fields = $this->getEntityFields();\n\n\t\t$result = [];\n\t\tforeach ($fields as $entityClass => $entityFields) {\n\t\t\t$registerArgumentsSet = new RegisterArgumentsSet(static::SET_ENTITY_FIELDS . ':' . $entityClass, $entityFields);\n\t\t\t$result[$registerArgumentsSet->key()] = $registerArgumentsSet;\n\n\t\t\tforeach (static::$methods as $method => $position) {\n\t\t\t\t$entityMethod = '\\\\' . $entityClass . '::' . $method . '()';\n\t\t\t\t$directive = new ExpectedArguments($entityMethod, $position, [$registerArgumentsSet]);\n\t\t\t\t$result[$directive->key()] = $directive;\n\t\t\t}\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<array<string>>\n\t */\n\tprotected function getEntityFields(): array {\n\t\t$modelFields = [];\n\n\t\t$models = $this->collectModels();\n\t\tforeach ($models as $model => $className) {\n\t\t\t$fields = [];\n\t\t\t$tableObject = null;\n\t\t\ttry {\n\t\t\t\t/** @var \\Cake\\ORM\\Table $tableObject */\n\t\t\t\t$tableObject = new $className();\n\t\t\t\t$fields = $tableObject->getSchema()->columns();\n\n\t\t\t} catch (Throwable $exception) {\n\t\t\t\t// Do nothing\n\t\t\t}\n\n\t\t\tif ($tableObject) {\n\t\t\t\ttry {\n\t\t\t\t\t$fieldsFromRelations = $this->addFromRelations($tableObject);\n\t\t\t\t\t$fields = array_merge($fields, $fieldsFromRelations);\n\t\t\t\t\t$fields = array_unique($fields);\n\t\t\t\t} catch (Throwable $exception) {\n\t\t\t\t\t// Do nothing\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$entityClass = $tableObject ? $tableObject->getEntityClass() : null;\n\t\t\ttry {\n\t\t\t\t/** @var \\Cake\\Datasource\\EntityInterface $entityObject */\n\t\t\t\t$entityObject = new $entityClass();\n\t\t\t\t$visibleFields = $entityObject->getVisible();\n\t\t\t\t$virtualFields = $entityObject->getVirtual();\n\t\t\t\t$fields = array_merge($fields, $virtualFields, $visibleFields);\n\t\t\t\t$fields = array_unique($fields);\n\t\t\t} catch (Throwable $exception) {\n\t\t\t\t// Do nothing\n\t\t\t}\n\n\t\t\tif (!$fields) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$list = [];\n\t\t\tforeach ($fields as $field) {\n\t\t\t\t$list[$field] = StringName::create($field);\n\t\t\t}\n\n\t\t\tksort($list);\n\n\t\t\t$modelFields[$entityClass] = $list;\n\t\t}\n\n\t\treturn $modelFields;\n\t}\n\n\t/**\n\t * @param \\Cake\\ORM\\Table $table\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addFromRelations(Table $table): array {\n\t\t$fields = [];\n\n\t\t/** @var \\Cake\\ORM\\AssociationCollection<\\Cake\\ORM\\Association> $associations */\n\t\t$associations = $table->associations();\n\n\t\tforeach ($associations as $association) {\n\t\t\t$fields[] = $association->getProperty();\n\t\t}\n\n\t\treturn $fields;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/EnvTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass EnvTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tprotected const METHOD_ENV = '\\\\' . 'env()';\n\n\t/**\n\t * Keys from Web based request, will be merged with CLI ones.\n\t *\n\t * @var array<string>\n\t */\n\tprotected static array $keys = [\n\t\t'HTTP_HOST',\n\t\t'HTTPS',\n\t\t'REMOTE_ADDR',\n\t\t'REMOTE_PORT',\n\t\t'DOCUMENT_ROOT',\n\t\t'DOCUMENT_URI',\n\t\t'PHP_SELF',\n\t\t'CGI_MODE',\n\t\t'SCRIPT_NAME',\n\t\t'HTTP_ACCEPT_LANGUAGE',\n\t\t'HTTP_ACCEPT_ENCODING',\n\t\t'HTTP_ACCEPT',\n\t\t'HTTP_COOKIE',\n\t\t'HTTP_USER_AGENT',\n\t\t'HTTP_CONNECTION',\n\t\t'HOME',\n\t\t'REDIRECT_STATUS',\n\t\t'SERVER_NAME',\n\t\t'SERVER_PORT',\n\t\t'SERVER_PROTOCOL',\n\t\t'GATEWAY_INTERFACE',\n\t\t'REQUEST_SCHEME',\n\t\t'REQUEST_URI',\n\t\t'REQUEST_METHOD',\n\t\t'CONTENT_LENGTH',\n\t\t'CONTENT_TYPE',\n\t\t'QUERY_STRING',\n\t\t'REQUEST_TIME',\n\t\t'SCRIPT_FILENAME',\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$keys = $this->envKeys();\n\n\t\tksort($keys);\n\n\t\t$method = static::METHOD_ENV;\n\t\t$directive = new ExpectedArguments($method, 0, $keys);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<\\IdeHelper\\ValueObject\\StringName>\n\t */\n\tprotected function envKeys(): array {\n\t\t$keys = array_keys($_SERVER);\n\t\t$keys = array_merge($keys, static::$keys);\n\t\t$keys = array_unique($keys);\n\n\t\t$list = [];\n\n\t\tforeach ($keys as $key) {\n\t\t\tif (str_starts_with($key, '_')) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$list[$key] = StringName::create($key);\n\t\t}\n\n\t\treturn $list;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/FixtureTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass FixtureTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tprotected const METHOD_ADD_FIXTURE = '\\\\' . TestCase::class . '::addFixture()';\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$fixtures = $this->getFixtures();\n\n\t\t$method = static::METHOD_ADD_FIXTURE;\n\t\t$directive = new ExpectedArguments($method, 0, $fixtures);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<\\IdeHelper\\ValueObject\\StringName>\n\t */\n\tprotected function getFixtures(): array {\n\t\t$list = [];\n\n\t\t$fixtures = [];\n\t\t$fixtureFolder = ROOT . DS . 'tests' . DS . 'Fixture' . DS;\n\t\t$fixtures += $this->parseFixtures($fixtureFolder, 'app');\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$path = Plugin::path($plugin);\n\t\t\t$fixtureFolder = $path . 'tests' . DS . 'Fixture' . DS;\n\t\t\t$fixtures += $this->parseFixtures($fixtureFolder, 'plugin.' . $plugin);\n\t\t}\n\n\t\t$fixtureFolder = ROOT . DS . 'vendor' . DS . 'cakephp' . DS . 'cakephp' . DS . 'tests' . DS . 'Fixture' . DS;\n\t\t$fixtures += $this->parseFixtures($fixtureFolder, 'core');\n\n\t\tforeach ($fixtures as $fixture) {\n\t\t\t$list[$fixture] = StringName::create($fixture);\n\t\t}\n\n\t\tksort($list);\n\n\t\treturn $list;\n\t}\n\n\t/**\n\t * @param string $fixtureFolder\n\t * @param string $domain\n\t * @param string|null $subFolder\n\t *\n\t * @return array<string>\n\t */\n\tprotected function parseFixtures(string $fixtureFolder, string $domain, ?string $subFolder = null): array {\n\t\tif (!is_dir($fixtureFolder)) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$folder = new Folder($fixtureFolder);\n\t\t$content = $folder->read();\n\n\t\t$fixtures = [];\n\t\tforeach ($content[1] as $file) {\n\t\t\tif (!str_ends_with($file, 'Fixture.php')) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$fixture = substr($file, 0, -11);\n\t\t\tif ($subFolder) {\n\t\t\t\t$fixture = str_replace(DS, '/', $subFolder) . '/' . $fixture;\n\t\t\t}\n\t\t\t$fixture = $domain . '.' . $fixture;\n\n\t\t\t$fixtures[$fixture] = $fixture;\n\t\t}\n\n\t\tforeach ($content[0] as $folder) {\n\t\t\t$fixtures += $this->parseFixtures($fixtureFolder . $folder . DS, $domain, $folder);\n\t\t}\n\n\t\treturn $fixtures;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/FormHelperTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\ORM\\TableRegistry;\nuse Cake\\View\\Helper\\FormHelper;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\ValueObject\\StringName;\nuse ReflectionClass;\nuse Throwable;\n\nclass FormHelperTask extends ModelTask {\n\n\tpublic const CLASS_FORM_HELPER = FormHelper::class;\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$list = $this->collectFieldNames();\n\n\t\tksort($list);\n\n\t\t$method = '\\\\' . static::CLASS_FORM_HELPER . '::control()';\n\t\t$directive = new ExpectedArguments($method, 0, $list);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectFieldNames(): array {\n\t\t$models = $this->collectModels();\n\n\t\t$allFields = [];\n\t\tforeach ($models as $model => $className) {\n\t\t\t/** @phpstan-var class-string<object>|null $tableClass */\n\t\t\t$tableClass = App::className($model, 'Model/Table', 'Table');\n\t\t\tif (!$tableClass) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$tableReflection = new ReflectionClass($tableClass);\n\t\t\tif (!$tableReflection->isInstantiable()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t$modelObject = TableRegistry::getTableLocator()->get($model);\n\t\t\t\t$fields = $modelObject->getSchema()->columns();\n\n\t\t\t} catch (Throwable $exception) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$allFields = array_merge($allFields, $fields);\n\t\t}\n\n\t\t$allFields = array_unique($allFields);\n\n\t\t$list = [];\n\t\tforeach ($allFields as $field) {\n\t\t\t$list[$field] = StringName::create($field);\n\t\t}\n\n\t\treturn $list;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/HelperTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\View\\View;\nuse Cake\\View\\ViewBuilder;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\ClassName;\n\nclass HelperTask implements TaskInterface {\n\n\tprotected const CLASS_VIEW = View::class;\n\tprotected const CLASS_VIEW_BUILDER = ViewBuilder::class;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $overrideMethods = [\n\t\t'\\\\' . self::CLASS_VIEW . '::loadHelper(0)',\n\t\t'\\\\' . self::CLASS_VIEW . '::addHelper(0)',\n\t];\n\n\t/**\n\t * @var string\n\t */\n\tprotected const METHOD_VIEW_BUILDER = '\\\\' . self::CLASS_VIEW_BUILDER . '::addHelper()';\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$helpers = $this->collectHelpers();\n\n\t\t$map = [];\n\t\tforeach ($helpers as $name => $className) {\n\t\t\t$map[$name] = ClassName::create($className);\n\t\t}\n\t\tksort($map);\n\n\t\t$result = [];\n\n\t\tforeach ($this->overrideMethods as $method) {\n\t\t\t$directive = new Override($method, $map);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\t$list = [];\n\t\tforeach ($helpers as $name => $className) {\n\t\t\t$list[$name] = \"'$name'\";\n\t\t}\n\t\tksort($list);\n\n\t\t$directive = new ExpectedArguments(static::METHOD_VIEW_BUILDER, 0, $list);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectHelpers(): array {\n\t\t$helpers = [];\n\n\t\t$folders = array_merge(App::core('View/Helper'), AppPath::get('View/Helper'));\n\t\tforeach ($folders as $folder) {\n\t\t\t$helpers = $this->addHelpers($helpers, $folder);\n\t\t}\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$folders = AppPath::get('View/Helper', $plugin);\n\t\t\tforeach ($folders as $folder) {\n\t\t\t\t$helpers = $this->addHelpers($helpers, $folder, $plugin);\n\t\t\t}\n\t\t}\n\n\t\treturn $helpers;\n\t}\n\n\t/**\n\t * @param array<string> $helpers\n\t * @param string $folder\n\t * @param string|null $plugin\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addHelpers(array $helpers, $folder, $plugin = null) {\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\tpreg_match('/^(.+)Helper\\.php$/', $file, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$name = $matches[1];\n\t\t\tif ($plugin) {\n\t\t\t\t$name = $plugin . '.' . $name;\n\t\t\t}\n\n\t\t\t$className = App::className($name, 'View/Helper', 'Helper');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$helpers[$name] = $className;\n\t\t}\n\n\t\treturn $helpers;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/LayoutTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Core\\App;\nuse Cake\\View\\ViewBuilder;\nuse DirectoryIterator;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\StringName;\nuse RecursiveRegexIterator;\nuse RegexIterator;\n\nclass LayoutTask implements TaskInterface {\n\n\tpublic const CLASS_VIEW_BUILDER = ViewBuilder::class;\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$layouts = $this->collectLayouts();\n\t\t$list = [];\n\t\tforeach ($layouts as $layout) {\n\t\t\t$list[$layout] = StringName::create($layout);\n\t\t}\n\n\t\tksort($list);\n\n\t\t$method = '\\\\' . static::CLASS_VIEW_BUILDER . '::setLayout()';\n\t\t$directive = new ExpectedArguments($method, 0, $list);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectLayouts(): array {\n\t\t$paths = App::path('templates');\n\n\t\t$result = [];\n\t\t$result = $this->addLayouts($result, $paths);\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$paths = App::path('templates', $plugin);\n\t\t\t$result = $this->addLayouts($result, $paths, $plugin);\n\t\t}\n\n\t\tsort($result);\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param array<string> $result\n\t * @param array<string> $paths\n\t * @param string|null $plugin\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addLayouts(array $result, array $paths, ?string $plugin = null): array {\n\t\tforeach ($paths as $path) {\n\t\t\t$path .= 'layout' . DS;\n\t\t\tif (!is_dir($path)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$directory = new DirectoryIterator($path);\n\t\t\t$regexIterator = new RegexIterator($directory, '/^.+\\.php$/i', RecursiveRegexIterator::GET_MATCH);\n\n\t\t\tforeach ($regexIterator as $file) {\n\t\t\t\t$name = str_replace($path, '', $file[0]);\n\t\t\t\t$name = substr($name, 0, -4);\n\t\t\t\t$name = str_replace(DS, '/', $name);\n\t\t\t\tif ($plugin) {\n\t\t\t\t\t$name = $plugin . '.' . $name;\n\t\t\t\t}\n\t\t\t\t$result[] = $name;\n\t\t\t}\n\t\t}\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/MailerTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Mailer\\MailerAwareTrait;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\ClassName;\n\nclass MailerTask implements TaskInterface {\n\n\tpublic const CLASS_MAILER = MailerAwareTrait::class;\n\n\tprotected static string $alias = '\\\\' . self::CLASS_MAILER . '::getMailer(0)';\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$map = [];\n\n\t\t$mailers = $this->collectMailers();\n\t\tforeach ($mailers as $name => $className) {\n\t\t\t$map[$name] = ClassName::create($className);\n\t\t}\n\n\t\tksort($map);\n\n\t\t$result = [];\n\t\tif ($map) {\n\t\t\t$directive = new Override(static::$alias, $map);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectMailers(): array {\n\t\t$mailers = [];\n\n\t\t$folders = AppPath::get('Mailer');\n\t\tforeach ($folders as $folder) {\n\t\t\t$mailers = $this->addMailers($mailers, $folder);\n\t\t}\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$folders = AppPath::get('Mailer', $plugin);\n\t\t\tforeach ($folders as $folder) {\n\t\t\t\t$mailers = $this->addMailers($mailers, $folder, $plugin);\n\t\t\t}\n\t\t}\n\n\t\treturn $mailers;\n\t}\n\n\t/**\n\t * @param array<string> $components\n\t * @param string $folder\n\t * @param string|null $plugin\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addMailers(array $components, $folder, $plugin = null) {\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\tpreg_match('/^(.+)Mailer\\.php$/', $file, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$name = $matches[1];\n\t\t\tif ($plugin) {\n\t\t\t\t$name = $plugin . '.' . $name;\n\t\t\t}\n\n\t\t\t$className = App::className($name, 'Mailer', 'Mailer');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$components[$name] = $className;\n\t\t}\n\n\t\treturn $components;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/ModelTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\ORM\\Table;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\ClassName;\nuse ReflectionClass;\n\nclass ModelTask implements TaskInterface {\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $aliases = [\n\t\t'\\Cake\\ORM\\Locator\\LocatorInterface::get(0)',\n\t\t'\\Cake\\ORM\\Locator\\LocatorAwareTrait::fetchTable(0)',\n\t\t'\\Cake\\Datasource\\ModelAwareTrait::fetchModel(0)',\n\t];\n\n\t/**\n\t * Buffer\n\t *\n\t * @var array<string, string>|null\n\t */\n\tprotected static ?array $models = null;\n\n\tpublic function __construct() {\n\t\t$this->clearBuffer();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic static function clearBuffer(): void {\n\t\tstatic::$models = null;\n\t}\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$map = [];\n\n\t\t$models = $this->collectModels();\n\t\tforeach ($models as $model => $className) {\n\t\t\t$map[$model] = ClassName::create($className);\n\t\t}\n\n\t\tksort($map);\n\n\t\t$result = [];\n\t\tforeach ($this->aliases as $alias) {\n\t\t\t$directive = new Override($alias, $map);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string, string>\n\t */\n\tprotected function collectModels(): array {\n\t\tif (static::$models !== null) {\n\t\t\treturn static::$models;\n\t\t}\n\n\t\t$models = [];\n\n\t\t$folders = AppPath::get('Model/Table');\n\t\tforeach ($folders as $folder) {\n\t\t\t$models = $this->addModels($models, $folder);\n\t\t}\n\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$folders = AppPath::get('Model/Table', $plugin);\n\t\t\tforeach ($folders as $folder) {\n\t\t\t\t$models = $this->addModels($models, $folder, $plugin);\n\t\t\t}\n\t\t}\n\n\t\tstatic::$models = $models;\n\n\t\treturn $models;\n\t}\n\n\t/**\n\t * @param array<string, string> $models\n\t * @param string $folder\n\t * @param string|null $plugin\n\t *\n\t * @return array<string, string>\n\t */\n\tprotected function addModels(array $models, $folder, $plugin = null) {\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\tpreg_match('/^(.+)Table\\.php$/', $file, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$model = $matches[1];\n\t\t\tif ($plugin) {\n\t\t\t\t$model = $plugin . '.' . $model;\n\t\t\t}\n\n\t\t\t/** @phpstan-var class-string<object>|null $className */\n\t\t\t$className = App::className($model, 'Model/Table', 'Table');\n\t\t\tif (!$className) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$reflectionClass = new ReflectionClass($className);\n\t\t\tif (!$reflectionClass->isInstantiable() || !$reflectionClass->isSubclassOf(Table::class)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$models[$model] = $className;\n\t\t}\n\n\t\treturn $models;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/PluginTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Core\\PluginApplicationInterface;\nuse Cake\\Http\\BaseApplication;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\ValueObject\\ClassName;\n\nclass PluginTask implements TaskInterface {\n\n\t/**\n\t * We need to use this until PhpStorm fixed the issue around concrete classes here\n\t */\n\tpublic const INTERFACE_APPLICATION = PluginApplicationInterface::class;\n\n\tpublic const CLASS_APPLICATION = BaseApplication::class;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $aliases = [\n\t\t'\\\\' . self::INTERFACE_APPLICATION . '::addPlugin(0)',\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$map = [];\n\n\t\t$plugins = $this->collectPlugins();\n\t\tforeach ($plugins as $name) {\n\t\t\t$map[$name] = ClassName::create(static::CLASS_APPLICATION);\n\t\t}\n\n\t\tksort($map);\n\n\t\t$result = [];\n\t\tforeach ($this->aliases as $alias) {\n\t\t\t$directive = new Override($alias, $map);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * Read from PluginCollection loaded config.\n\t *\n\t * @return array<string>\n\t */\n\tprotected function collectPlugins(): array {\n\t\t$plugins = (array)Configure::read('plugins');\n\n\t\t$names = array_keys($plugins);\n\n\t\tsort($names);\n\n\t\treturn $names;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/RequestTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Http\\ServerRequest;\nuse Cake\\Http\\Session;\nuse Cake\\Routing\\Route\\Route;\nuse Cake\\Routing\\Router;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\ClassName;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass RequestTask implements TaskInterface {\n\n\tpublic const CLASS_REQUEST = ServerRequest::class;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected static array $paramKeys = [\n\t\t'controller',\n\t\t'action',\n\t\t'plugin',\n\t\t'prefix',\n\t\t'pass',\n\t\t'_matchedRoute',\n\t\t'_ext',\n\t];\n\n\t/**\n\t * @var array<string, string>\n\t */\n\tprotected static array $attributeKeys = [\n\t\t'csrfToken' => 'string',\n\t\t'params' => 'array',\n\t\t'webroot' => 'string',\n\t\t'base' => 'string',\n\t\t'here' => 'string',\n\t\t'cspScriptNonce' => 'string',\n\t\t'cspStyleNonce' => 'string',\n\t\t'formTokenData' => 'array',\n\t\t'paging' => 'array',\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$list = $this->collectParamKeys();\n\n\t\t$method = '\\\\' . static::CLASS_REQUEST . '::getParam()';\n\t\t$directive = new ExpectedArguments($method, 0, $list);\n\t\t$result[$directive->key()] = $directive;\n\n\t\t$map = $this->collectAttributes();\n\n\t\t$method = '\\\\' . static::CLASS_REQUEST . '::getAttribute(0)';\n\t\t$directive = new Override($method, $map);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string, \\IdeHelper\\ValueObject\\StringName>\n\t */\n\tprotected function collectParamKeys(): array {\n\t\t$keys = [];\n\t\tforeach (static::$paramKeys as $key) {\n\t\t\t$keys[$key] = StringName::create($key);\n\t\t}\n\n\t\tksort($keys);\n\n\t\treturn $keys;\n\t}\n\n\t/**\n\t * @return array<string, mixed>\n\t */\n\tprotected function collectAttributes(): array {\n\t\t$request = Router::getRequest() ?: new ServerRequest();\n\t\t$defaults = array_keys($request->getAttributes());\n\n\t\t$attributes = [\n\t\t\t'route' => ClassName::create(Route::class),\n\t\t\t'session' => ClassName::create(Session::class),\n\t\t];\n\t\tforeach (static::$attributeKeys as $key => $type) {\n\t\t\t$attributes[$key] = StringName::create($type);\n\t\t}\n\n\t\t$attributes = $this->addAttributesFromPlugins($attributes);\n\n\t\tforeach ($defaults as $default) {\n\t\t\tif (isset($attributes[$default])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$attributes[$default] = StringName::create('');\n\t\t}\n\n\t\tksort($attributes);\n\n\t\treturn $attributes;\n\t}\n\n\t/**\n\t * @param array<string, mixed> $attributes\n\t * @return array<string, mixed>\n\t */\n\tprotected function addAttributesFromPlugins(array $attributes): array {\n\t\tif (Plugin::isLoaded('Authorization')) {\n\t\t\t$attributes['identity'] = ClassName::create('Authorization\\IdentityInterface');\n\t\t} elseif (Plugin::isLoaded('Authentication')) {\n\t\t\t$attributes['identity'] = ClassName::create('Authentication\\IdentityInterface');\n\t\t}\n\t\tif (Plugin::isLoaded('Authentication')) {\n\t\t\t$attributes['authentication'] = ClassName::create('Authentication\\AuthenticationService');\n\t\t\t$attributes['authenticationResult'] = ClassName::create('Authentication\\Authenticator\\Result');\n\t\t}\n\n\t\treturn $attributes;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/RoutePathTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Routing\\Router;\nuse Cake\\View\\Helper\\HtmlHelper;\nuse Cake\\View\\Helper\\UrlHelper;\nuse IdeHelper\\Filesystem\\Folder;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\Utility\\AppPath;\nuse IdeHelper\\Utility\\ControllerActionParser;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass RoutePathTask implements TaskInterface {\n\n\tpublic const CLASS_ROUTER = Router::class;\n\tpublic const CLASS_URL_HELPER = UrlHelper::class;\n\tpublic const CLASS_HTML_HELPER = HtmlHelper::class;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const SET_ROUTE_PATHS = 'routePaths';\n\n\tprotected ControllerActionParser $controllerActionParser;\n\n\tpublic function __construct() {\n\t\t$this->controllerActionParser = new ControllerActionParser();\n\t}\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$list = $this->collectPaths();\n\t\t$registerArgumentsSet = new RegisterArgumentsSet(static::SET_ROUTE_PATHS, $list);\n\t\t$result[$registerArgumentsSet->key()] = $registerArgumentsSet;\n\n\t\t$method = '\\\\' . static::CLASS_ROUTER . '::pathUrl()';\n\t\t$directive = new ExpectedArguments($method, 0, [$registerArgumentsSet]);\n\t\t$result[$directive->key()] = $directive;\n\n\t\t$method = '\\\\' . static::CLASS_URL_HELPER . '::buildFromPath()';\n\t\t$directive = new ExpectedArguments($method, 0, [$registerArgumentsSet]);\n\t\t$result[$directive->key()] = $directive;\n\n\t\t$method = '\\\\' . static::CLASS_HTML_HELPER . '::linkFromPath()';\n\t\t$directive = new ExpectedArguments($method, 1, [$registerArgumentsSet]);\n\t\t$result[$directive->key()] = $directive;\n\n\t\t$method = '\\\\urlArray()';\n\t\t$directive = new ExpectedArguments($method, 0, [$registerArgumentsSet]);\n\t\t$result[$directive->key()] = $directive;\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function collectPaths(): array {\n\t\t$plugins = Plugin::all();\n\n\t\t$controllerPaths = AppPath::get('Controller');\n\n\t\t$paths = [];\n\t\tforeach ($controllerPaths as $controllerPath) {\n\t\t\t$paths += $this->_paths($controllerPath);\n\t\t}\n\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$pluginControllerPath = Plugin::classPath($plugin) . 'Controller' . DS;\n\t\t\t$paths += $this->_paths($pluginControllerPath, $plugin);\n\t\t}\n\n\t\tksort($paths);\n\n\t\treturn $paths;\n\t}\n\n\t/**\n\t * @param string $folder\n\t * @param string|null $plugin\n\t * @param string|null $prefix\n\t * @return array<string>\n\t */\n\tprotected function _paths(string $folder, ?string $plugin = null, ?string $prefix = null): array {\n\t\t$paths = [];\n\n\t\t$folderContent = (new Folder($folder))->read(Folder::SORT_NAME, true);\n\n\t\tforeach ($folderContent[1] as $file) {\n\t\t\t$name = basename($file);\n\t\t\tpreg_match('/^(.+)Controller\\.php$/', $name, $matches);\n\t\t\tif (!$matches || $matches[1] === 'App') {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$controllerName = $matches[1];\n\n\t\t\t$actions = $this->controllerActionParser->parse($folder . $file);\n\t\t\tforeach ($actions as $action) {\n\t\t\t\t$routePath = $controllerName . '::' . $action;\n\t\t\t\tif ($prefix) {\n\t\t\t\t\t$routePath = $prefix . '/' . $routePath;\n\t\t\t\t}\n\t\t\t\tif ($plugin) {\n\t\t\t\t\t$routePath = $plugin . '.' . $routePath;\n\t\t\t\t}\n\n\t\t\t\t$paths[$routePath] = StringName::create($routePath);\n\t\t\t}\n\t\t}\n\n\t\tforeach ($folderContent[0] as $subFolder) {\n\t\t\t$prefixes = (array)Configure::read('IdeHelper.prefixes') ?: null;\n\n\t\t\tif ($prefixes !== null && !in_array($subFolder, $prefixes, true)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$sub = $this->_paths($folder . $subFolder . DS, $plugin, $subFolder);\n\t\t\t$paths += $sub;\n\t\t}\n\n\t\treturn $paths;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/TableAssociationTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\ORM\\Association\\BelongsTo;\nuse Cake\\ORM\\Association\\BelongsToMany;\nuse Cake\\ORM\\Association\\HasMany;\nuse Cake\\ORM\\Association\\HasOne;\nuse Cake\\ORM\\Table;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\ValueObject\\ClassName;\n\nclass TableAssociationTask extends ModelTask {\n\n\tpublic const CLASS_TABLE = Table::class;\n\n\t/**\n\t * @var array<string, string>\n\t */\n\tprotected array $aliases = [\n\t\t'\\\\' . self::CLASS_TABLE . '::belongsTo(0)' => BelongsTo::class,\n\t\t'\\\\' . self::CLASS_TABLE . '::hasOne(0)' => HasOne::class,\n\t\t'\\\\' . self::CLASS_TABLE . '::hasMany(0)' => HasMany::class,\n\t\t'\\\\' . self::CLASS_TABLE . '::belongToMany(0)' => BelongsToMany::class,\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$models = $this->collectModels();\n\n\t\t$result = [];\n\t\tforeach ($this->aliases as $alias => $className) {\n\t\t\t$map = [];\n\t\t\tforeach ($models as $model => $modelClassName) {\n\t\t\t\t$map[$model] = ClassName::create($className);\n\t\t\t}\n\n\t\t\tksort($map);\n\n\t\t\t$directive = new Override($alias, $map);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/TableFinderTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Datasource\\QueryInterface;\nuse Cake\\ORM\\Association;\nuse Cake\\ORM\\Query\\SelectQuery;\nuse Cake\\ORM\\Table;\nuse Cake\\ORM\\TableRegistry;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\ValueObject\\ClassName;\nuse ReflectionClass;\nuse ReflectionException;\nuse ReflectionMethod;\nuse Throwable;\n\nclass TableFinderTask extends ModelTask {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const INTERFACE_QUERY = QueryInterface::class;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CLASS_TABLE = Table::class;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CLASS_ASSOCIATION = Association::class;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CLASS_QUERY = SelectQuery::class;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $cache = [];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$finders = $this->collectFinders();\n\t\tforeach ($finders as $className => $methods) {\n\t\t\t$map = [];\n\t\t\tforeach ($methods as $method) {\n\t\t\t\t$map[$method] = ClassName::create(static::CLASS_QUERY);\n\t\t\t}\n\n\t\t\tksort($map);\n\n\t\t\t$method = '\\\\' . $className . '::find(0)';\n\t\t\t$directive = new Override($method, $map);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string, array<string>>\n\t */\n\tprotected function collectFinders(): array {\n\t\t$baseFinders = $this->getFinderMethods(static::CLASS_TABLE);\n\t\t$customFinders = $this->getCustomFinders();\n\n\t\t$allFinders = array_merge($baseFinders, $customFinders);\n\t\t$allFinders = array_unique($allFinders);\n\n\t\tsort($allFinders);\n\n\t\t$finders = [];\n\t\t$finders[static::CLASS_TABLE] = $allFinders;\n\t\t$finders[static::CLASS_ASSOCIATION] = $allFinders;\n\t\t$finders[static::INTERFACE_QUERY] = $allFinders;\n\n\t\treturn $finders;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tprotected function getCustomFinders(): array {\n\t\t$models = $this->collectModels();\n\n\t\t$allFinders = [];\n\t\tforeach ($models as $model => $className) {\n\t\t\t$customFinders = $this->getFinderMethods($className);\n\n\t\t\t/** @phpstan-var class-string<object>|null $tableClass */\n\t\t\t$tableClass = App::className($model, 'Model/Table', 'Table');\n\t\t\tif (!$tableClass) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$tableReflection = new ReflectionClass($tableClass);\n\t\t\tif (!$tableReflection->isInstantiable()) {\n\t\t\t\t$allFinders = array_merge($allFinders, $customFinders);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t$modelObject = TableRegistry::getTableLocator()->get($model);\n\t\t\t\t$behaviors = $modelObject->behaviors();\n\n\t\t\t\t/** @var iterable<\\Cake\\ORM\\Behavior> $iterator */\n\t\t\t\t$iterator = $behaviors->getIterator();\n\t\t\t\tforeach ($iterator as $behavior) {\n\t\t\t\t\t$behaviorClass = get_class($behavior);\n\t\t\t\t\tif (in_array($behaviorClass, $this->cache, true)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t$this->cache[] = $behaviorClass;\n\n\t\t\t\t\tif ($behavior->implementedFinders()) {\n\t\t\t\t\t\t$customFinders = array_merge($customFinders, array_keys($behavior->implementedFinders()));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (Throwable $exception) {\n\t\t\t}\n\n\t\t\t$allFinders = array_merge($allFinders, $customFinders);\n\t\t}\n\n\t\treturn array_unique($allFinders);\n\t}\n\n\t/**\n\t * Gets protected/private property of a class.\n\t *\n\t * So\n\t *   $this->invokeProperty($object, '_foo');\n\t * is equal to\n\t *   $object->_foo\n\t * (assuming the property was directly publicly accessible)\n\t *\n\t * @param object $object Instantiated object that we want the property off.\n\t * @param string $name Property name to fetch.\n\t *\n\t * @return mixed Property value.\n\t */\n\tprotected function invokeProperty(&$object, string $name) {\n\t\t$reflection = new ReflectionClass(get_class($object));\n\t\tif (!$reflection->hasProperty($name)) {\n\t\t\treturn null;\n\t\t}\n\t\t$property = $reflection->getProperty($name);\n\n\t\treturn $property->getValue($object);\n\t}\n\n\t/**\n\t * @param string $className\n\t *\n\t * @return array<string>\n\t */\n\tprotected function getFinderMethods($className) {\n\t\t$result = [];\n\n\t\t$methods = get_class_methods($className);\n\t\tforeach ($methods as $method) {\n\t\t\t$result = $this->addMethod($result, $method, $className);\n\t\t}\n\n\t\tksort($result);\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @param array<string> $result\n\t * @param string $method\n\t * @param string $className\n\t *\n\t * @return array<string>\n\t */\n\tprotected function addMethod(array $result, $method, $className) {\n\t\t// We must exclude all find...By... patterns as possible false positives for now (refs https://github.com/cakephp/cakephp/issues/11240)\n\t\tif ($method === 'findOrCreate' || preg_match('/^find.*By[A-Z][a-zA-Z]+/', $method)) {\n\t\t\treturn $result;\n\t\t}\n\t\tif (!preg_match('/^find([A-Z][a-zA-Z]+)/', $method, $matches)) {\n\t\t\treturn $result;\n\t\t}\n\n\t\ttry {\n\t\t\t$methodReflection = new ReflectionMethod($className, $method);\n\t\t} catch (ReflectionException $e) {\n\t\t\treturn $result;\n\t\t}\n\n\t\t$parameters = $methodReflection->getParameters();\n\t\tif (count($parameters) < 1) {\n\t\t\treturn $result;\n\t\t}\n\n\t\t$name = lcfirst($matches[1]);\n\n\t\t$parameter = $parameters[0];\n\n\t\t/** @var \\ReflectionNamedType|\\ReflectionUnionType|null $parameterType */\n\t\t$parameterType = $parameter->getType();\n\t\tif (!$parameterType || !method_exists($parameterType, 'getName') || $parameterType->getName() !== SelectQuery::class) {\n\t\t\treturn $result;\n\t\t}\n\n\t\t$result[] = $name;\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/TaskInterface.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\ninterface TaskInterface {\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array;\n\n}\n"
  },
  {
    "path": "src/Generator/Task/TranslationKeyTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Utility\\Inflector;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\Utility\\Plugin;\nuse IdeHelper\\Utility\\TranslationParser;\nuse IdeHelper\\ValueObject\\StringName;\nuse RecursiveDirectoryIterator;\nuse RecursiveIteratorIterator;\nuse RecursiveRegexIterator;\nuse RegexIterator;\n\n/**\n * @link https://book.cakephp.org/5/en/core-libraries/global-constants-and-functions.html#global-functions\n */\nclass TranslationKeyTask implements TaskInterface {\n\n\tprotected TranslationParser $translationParser;\n\n\tpublic function __construct() {\n\t\t$this->translationParser = new TranslationParser();\n\t}\n\n\t/**\n\t * function __(string $singular, ...$args): string\n\t *\n\t * @var string\n\t */\n\tprotected const METHOD_DEFAULT = '__()';\n\n\t/**\n\t * function __d(string $domain, string $msg, ...$args): string\n\t *\n\t * @var string\n\t */\n\tprotected const METHOD_DOMAIN = '__d()';\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$translationKeys = $this->translationKeys();\n\n\t\t$domains = [];\n\t\t$domainKeys = [];\n\t\tforeach ($translationKeys as $domain => $keys) {\n\t\t\tif ($domain === 'default') {\n\t\t\t\t$method = '\\\\' . static::METHOD_DEFAULT;\n\t\t\t\t$directive = new ExpectedArguments($method, 0, $keys);\n\t\t\t\t$result[$directive->key()] = $directive;\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$domains[$domain] = StringName::create($domain);\n\t\t\t$domainKeys += $keys;\n\t\t}\n\n\t\tif ($domainKeys) {\n\t\t\tksort($domainKeys);\n\n\t\t\t$method = '\\\\' . static::METHOD_DOMAIN;\n\t\t\t$directive = new ExpectedArguments($method, 1, $domainKeys);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\t$domains = $this->completeDomains($domains);\n\n\t\tif ($domains) {\n\t\t\t$method = '\\\\' . static::METHOD_DOMAIN;\n\t\t\t$directive = new ExpectedArguments($method, 0, $domains);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<string, array<string, \\IdeHelper\\ValueObject\\StringName>>\n\t */\n\tprotected function translationKeys(): array {\n\t\t$translations = $this->parseTranslations();\n\n\t\t$translationsKeys = [];\n\t\tforeach ($translations as $domain => $array) {\n\t\t\t$result = [];\n\t\t\tforeach ($array as $key) {\n\t\t\t\t$result[$key] = StringName::create($key);\n\t\t\t}\n\n\t\t\tksort($result);\n\n\t\t\t$translationsKeys[$domain] = $result;\n\t\t}\n\n\t\tksort($translationsKeys);\n\n\t\treturn $translationsKeys;\n\t}\n\n\t/**\n\t * @return array<array<string>>\n\t */\n\tprotected function parseTranslations(): array {\n\t\t$keys = [];\n\n\t\t$localePaths = App::path('locales');\n\t\tforeach ($localePaths as $localePath) {\n\t\t\tif (!is_dir($localePath)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$directoryIterator = new RecursiveDirectoryIterator($localePath);\n\t\t\t$iterator = new RecursiveIteratorIterator($directoryIterator);\n\t\t\t$regexIterator = new RegexIterator($iterator, '/^.+\\.po/i', RecursiveRegexIterator::GET_MATCH);\n\n\t\t\tforeach ($regexIterator as $files) {\n\t\t\t\tforeach ($files as $file) {\n\t\t\t\t\tif (!file_exists($file)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t$domainKeys = $this->translationParser->parse($file);\n\n\t\t\t\t\t$domain = pathinfo($file, PATHINFO_FILENAME);\n\t\t\t\t\tif (!isset($keys[$domain])) {\n\t\t\t\t\t\t$keys[$domain] = [];\n\t\t\t\t\t}\n\t\t\t\t\t$keys[$domain] += $domainKeys;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t$plugins = Plugin::all();\n\t\t\tforeach ($plugins as $plugin) {\n\t\t\t\t$localePath = Plugin::path($plugin) . 'resources' . DIRECTORY_SEPARATOR . 'locales' . DIRECTORY_SEPARATOR;\n\n\t\t\t\tif (!is_dir($localePath)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t$directoryIterator = new RecursiveDirectoryIterator($localePath);\n\t\t\t\t$iterator = new RecursiveIteratorIterator($directoryIterator);\n\t\t\t\t$regexIterator = new RegexIterator($iterator, '/^.+\\.po/i', RecursiveRegexIterator::GET_MATCH);\n\n\t\t\t\tforeach ($regexIterator as $files) {\n\t\t\t\t\tforeach ($files as $file) {\n\t\t\t\t\t\tif (!file_exists($file)) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t$domainKeys = $this->translationParser->parse($file);\n\n\t\t\t\t\t\t$domain = pathinfo($file, PATHINFO_FILENAME);\n\t\t\t\t\t\tif (!isset($keys[$domain])) {\n\t\t\t\t\t\t\t$keys[$domain] = [];\n\t\t\t\t\t\t}\n\t\t\t\t\t\t$keys[$domain] += $domainKeys;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn $keys;\n\t}\n\n\t/**\n\t * @param array<\\IdeHelper\\ValueObject\\StringName> $domains\n\t *\n\t * @return array<\\IdeHelper\\ValueObject\\StringName>\n\t */\n\tprotected function completeDomains(array $domains): array {\n\t\t$plugins = Plugin::all();\n\t\tforeach ($plugins as $plugin) {\n\t\t\t$pieces = explode('/', $plugin);\n\t\t\tforeach ($pieces as $key => $piece) {\n\t\t\t\t$pieces[$key] = Inflector::underscore($piece);\n\t\t\t}\n\n\t\t\t$domain = implode('/', $pieces);\n\n\t\t\t// Issue of https://github.com/cakephp/docs/pull/6585 and for 5.0 to be resolved\n\t\t\tif (count($pieces) > 1) {\n\t\t\t\t$lastPiece = array_pop($pieces);\n\t\t\t\tunset($domains[$lastPiece]);\n\t\t\t}\n\n\t\t\t$domains[$domain] = StringName::create($domain);\n\t\t}\n\n\t\tif (!isset($domains['cake'])) {\n\t\t\t$domains['cake'] = StringName::create('cake');\n\t\t}\n\n\t\tksort($domains);\n\n\t\treturn $domains;\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/Task/ValidationTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator\\Task;\n\nuse Cake\\Validation\\Validator;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass ValidationTask implements TaskInterface {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const SET_VALIDATION_WHEN = 'validationWhen';\n\n\t/**\n\t * @var array<string, int>\n\t */\n\tprotected static array $methods = [\n\t\t'requirePresence' => 1,\n\t\t'allowEmptyFor' => 2,\n\t\t'allowEmptyString' => 2,\n\t\t'allowEmptyFile' => 2,\n\t\t'allowEmptyArray' => 2,\n\t\t'allowEmptyDate' => 2,\n\t\t'allowEmptyTime' => 2,\n\t\t'allowEmptyDateTime' => 2,\n\t\t'notEmptyString' => 2,\n\t\t'notEmptyFile' => 2,\n\t\t'notEmptyArray' => 2,\n\t\t'notEmptyDate' => 2,\n\t\t'notEmptyTime' => 2,\n\t\t'notEmptyDateTime' => 2,\n\t];\n\n\t/**\n\t * @return array<string, \\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function collect(): array {\n\t\t$result = [];\n\n\t\t$list = $this->getValidatorRequirePresence();\n\t\t$registerArgumentsSet = new RegisterArgumentsSet(static::SET_VALIDATION_WHEN, $list);\n\t\t$result[$registerArgumentsSet->key()] = $registerArgumentsSet;\n\n\t\tforeach (static::$methods as $method => $position) {\n\t\t\t$method = '\\\\' . Validator::class . '::' . $method . '()';\n\t\t\t$directive = new ExpectedArguments($method, $position, [$registerArgumentsSet]);\n\t\t\t$result[$directive->key()] = $directive;\n\t\t}\n\n\t\treturn $result;\n\t}\n\n\t/**\n\t * @return array<\\IdeHelper\\ValueObject\\ValueObjectInterface>\n\t */\n\tprotected function getValidatorRequirePresence(): array {\n\t\treturn [\n\t\t\tStringName::create('create'),\n\t\t\tStringName::create('update'),\n\t\t];\n\t}\n\n}\n"
  },
  {
    "path": "src/Generator/TaskCollection.php",
    "content": "<?php\n\nnamespace IdeHelper\\Generator;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Generator\\Task\\BehaviorTask;\nuse IdeHelper\\Generator\\Task\\CacheTask;\nuse IdeHelper\\Generator\\Task\\CellTask;\nuse IdeHelper\\Generator\\Task\\ComponentTask;\nuse IdeHelper\\Generator\\Task\\ConfigureTask;\nuse IdeHelper\\Generator\\Task\\ConnectionTask;\nuse IdeHelper\\Generator\\Task\\ConsoleHelperTask;\nuse IdeHelper\\Generator\\Task\\ConsoleTask;\nuse IdeHelper\\Generator\\Task\\DatabaseTableColumnNameTask;\nuse IdeHelper\\Generator\\Task\\DatabaseTableColumnTypeTask;\nuse IdeHelper\\Generator\\Task\\DatabaseTableTask;\nuse IdeHelper\\Generator\\Task\\DatabaseTypeTask;\nuse IdeHelper\\Generator\\Task\\ElementTask;\nuse IdeHelper\\Generator\\Task\\EntityTask;\nuse IdeHelper\\Generator\\Task\\EnvTask;\nuse IdeHelper\\Generator\\Task\\FixtureTask;\nuse IdeHelper\\Generator\\Task\\FormHelperTask;\nuse IdeHelper\\Generator\\Task\\HelperTask;\nuse IdeHelper\\Generator\\Task\\LayoutTask;\nuse IdeHelper\\Generator\\Task\\MailerTask;\nuse IdeHelper\\Generator\\Task\\ModelTask;\nuse IdeHelper\\Generator\\Task\\PluginTask;\nuse IdeHelper\\Generator\\Task\\RequestTask;\nuse IdeHelper\\Generator\\Task\\RoutePathTask;\nuse IdeHelper\\Generator\\Task\\TableAssociationTask;\nuse IdeHelper\\Generator\\Task\\TableFinderTask;\nuse IdeHelper\\Generator\\Task\\TaskInterface;\nuse IdeHelper\\Generator\\Task\\TranslationKeyTask;\nuse IdeHelper\\Generator\\Task\\ValidationTask;\nuse InvalidArgumentException;\n\nclass TaskCollection {\n\n\t/**\n\t * @var array<class-string<\\IdeHelper\\Generator\\Task\\TaskInterface>, class-string<\\IdeHelper\\Generator\\Task\\TaskInterface>>\n\t */\n\tprotected array $defaultTasks = [\n\t\tModelTask::class => ModelTask::class,\n\t\tBehaviorTask::class => BehaviorTask::class,\n\t\tComponentTask::class => ComponentTask::class,\n\t\tHelperTask::class => HelperTask::class,\n\t\tMailerTask::class => MailerTask::class,\n\t\tTableAssociationTask::class => TableAssociationTask::class,\n\t\tTableFinderTask::class => TableFinderTask::class,\n\t\tDatabaseTypeTask::class => DatabaseTypeTask::class,\n\t\tElementTask::class => ElementTask::class,\n\t\tLayoutTask::class => LayoutTask::class,\n\t\tPluginTask::class => PluginTask::class,\n\t\tValidationTask::class => ValidationTask::class,\n\t\tRoutePathTask::class => RoutePathTask::class,\n\t\tCacheTask::class => CacheTask::class,\n\t\tRequestTask::class => RequestTask::class,\n\t\tEnvTask::class => EnvTask::class,\n\t\tConsoleTask::class => ConsoleTask::class,\n\t\tConnectionTask::class => ConnectionTask::class,\n\t\tFormHelperTask::class => FormHelperTask::class,\n\t\tDatabaseTableTask::class => DatabaseTableTask::class,\n\t\tDatabaseTableColumnNameTask::class => DatabaseTableColumnNameTask::class,\n\t\tDatabaseTableColumnTypeTask::class => DatabaseTableColumnTypeTask::class,\n\t\tEntityTask::class => EntityTask::class,\n\t\tFixtureTask::class => FixtureTask::class,\n\t\tTranslationKeyTask::class => TranslationKeyTask::class,\n\t\tConfigureTask::class => ConfigureTask::class,\n\t\tCellTask::class => CellTask::class,\n\t\tConsoleHelperTask::class => ConsoleHelperTask::class,\n\t];\n\n\t/**\n\t * @var array<\\IdeHelper\\Generator\\Task\\TaskInterface>\n\t */\n\tprotected array $tasks = [];\n\n\t/**\n\t * @param array<string|\\IdeHelper\\Generator\\Task\\TaskInterface> $tasks\n\t */\n\tpublic function __construct(array $tasks = []) {\n\t\t$defaultTasks = $this->defaultTasks();\n\t\t$tasks += $defaultTasks;\n\n\t\tforeach ($tasks as $task) {\n\t\t\tif (!$task) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$this->add($task);\n\t\t}\n\t}\n\n\t/**\n\t * @return array<class-string<\\IdeHelper\\Generator\\Task\\TaskInterface>>\n\t */\n\tprotected function defaultTasks(): array {\n\t\t$tasks = (array)Configure::read('IdeHelper.generatorTasks') + $this->defaultTasks;\n\n\t\tforeach ($tasks as $k => $v) {\n\t\t\tif (is_numeric($k)) {\n\t\t\t\t$tasks[$v] = $v;\n\t\t\t\tunset($tasks[$k]);\n\t\t\t}\n\t\t}\n\n\t\treturn $tasks;\n\t}\n\n\t/**\n\t * Adds a task to the collection.\n\t *\n\t * @param \\IdeHelper\\Generator\\Task\\TaskInterface|string $task The task to map.\n\t * @throws \\InvalidArgumentException\n\t * @return $this\n\t */\n\tprotected function add($task) {\n\t\tif (is_string($task)) {\n\t\t\t$task = new $task();\n\t\t}\n\n\t\t$class = get_class($task);\n\t\tif (!$task instanceof TaskInterface) {\n\t\t\tthrow new InvalidArgumentException(\n\t\t\t\tsprintf('Cannot use `%s` as task, it is not implementing `%s`.', $class, TaskInterface::class),\n\t\t\t);\n\t\t}\n\n\t\t$this->tasks[$class] = $task;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array<\\IdeHelper\\Generator\\Task\\TaskInterface>\n\t */\n\tpublic function tasks(): array {\n\t\treturn $this->tasks;\n\t}\n\n\t/**\n\t * @return array<\\IdeHelper\\Generator\\Directive\\BaseDirective>\n\t */\n\tpublic function getMap(): array {\n\t\t$map = [];\n\t\tforeach ($this->tasks as $task) {\n\t\t\t$map += $task->collect();\n\t\t}\n\n\t\tksort($map);\n\n\t\treturn $map;\n\t}\n\n}\n"
  },
  {
    "path": "src/IdeHelperPlugin.php",
    "content": "<?php\n\nnamespace IdeHelper;\n\nuse Cake\\Console\\CommandCollection;\nuse Cake\\Core\\BasePlugin;\nuse IdeHelper\\Command\\Annotate\\AllCommand;\nuse IdeHelper\\Command\\Annotate\\CallbacksCommand;\nuse IdeHelper\\Command\\Annotate\\ClassesCommand;\nuse IdeHelper\\Command\\Annotate\\CommandsCommand;\nuse IdeHelper\\Command\\Annotate\\ComponentsCommand;\nuse IdeHelper\\Command\\Annotate\\ControllersCommand;\nuse IdeHelper\\Command\\Annotate\\HelpersCommand;\nuse IdeHelper\\Command\\Annotate\\ModelsCommand;\nuse IdeHelper\\Command\\Annotate\\TemplatesCommand;\nuse IdeHelper\\Command\\Annotate\\ViewCommand;\nuse IdeHelper\\Command\\GenerateCodeCompletionCommand;\nuse IdeHelper\\Command\\GeneratePhpStormMetaCommand;\nuse IdeHelper\\Command\\IlluminateCommand;\n\n/**\n * Plugin for IdeHelper\n */\nclass IdeHelperPlugin extends BasePlugin {\n\n\t/**\n\t * Define the console commands for an application.\n\t *\n\t * @param \\Cake\\Console\\CommandCollection $commands The CommandCollection to add commands into.\n\t * @return \\Cake\\Console\\CommandCollection The updated collection.\n\t */\n\tpublic function console(CommandCollection $commands): CommandCollection {\n\t\t$commands->add('annotate models', ModelsCommand::class);\n\t\t$commands->add('annotate view', ViewCommand::class);\n\t\t$commands->add('annotate helpers', HelpersCommand::class);\n\t\t$commands->add('annotate components', ComponentsCommand::class);\n\t\t$commands->add('annotate templates', TemplatesCommand::class);\n\t\t$commands->add('annotate controllers', ControllersCommand::class);\n\t\t$commands->add('annotate commands', CommandsCommand::class);\n\t\t$commands->add('annotate classes', ClassesCommand::class);\n\t\t$commands->add('annotate callbacks', CallbacksCommand::class);\n\t\t$commands->add('annotate all', AllCommand::class);\n\n\t\t$commands->add('generate code_completion', GenerateCodeCompletionCommand::class);\n\t\t$commands->add('generate phpstorm', GeneratePhpStormMetaCommand::class);\n\t\t$commands->add('illuminate code', IlluminateCommand::class);\n\n\t\treturn $commands;\n\t}\n\n}\n"
  },
  {
    "path": "src/Illuminator/Illuminator.php",
    "content": "<?php\n\nnamespace IdeHelper\\Illuminator;\n\nuse IdeHelper\\Filesystem\\Folder;\n\nclass Illuminator {\n\n\tprotected TaskCollection $taskCollection;\n\n\t/**\n\t * @param \\IdeHelper\\Illuminator\\TaskCollection $taskCollection\n\t */\n\tpublic function __construct(TaskCollection $taskCollection) {\n\t\t$this->taskCollection = $taskCollection;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string|null $filter\n\t * @return int\n\t */\n\tpublic function illuminate($path, $filter) {\n\t\t$files = $this->getFiles($path);\n\n\t\t$count = 0;\n\t\tforeach ($files as $file) {\n\t\t\t$name = pathinfo($file, PATHINFO_FILENAME);\n\t\t\tif ($this->shouldSkip($name, $filter)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!$this->taskCollection->run($file)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$count++;\n\t\t}\n\n\t\treturn $count;\n\t}\n\n\t/**\n\t * @param string $fileName\n\t * @param string|null $filter\n\t *\n\t * @return bool\n\t */\n\tprotected function shouldSkip($fileName, $filter) {\n\t\tif (!$filter) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn !preg_match('/' . preg_quote($filter, '/') . '/i', $fileName);\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return array<string>\n\t */\n\tprotected function getFiles($path) {\n\t\t$folder = new Folder($path);\n\t\t$result = $folder->findRecursive('.*\\.php', true);\n\n\t\treturn $result;\n\t}\n\n}\n"
  },
  {
    "path": "src/Illuminator/Task/AbstractTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Illuminator\\Task;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Core\\InstanceConfigTrait;\nuse IdeHelper\\Annotator\\Traits\\FileTrait;\nuse PHP_CodeSniffer\\Config;\n\n$composerVendorDir = getcwd() . DS . 'vendor';\n$codesnifferDir = 'squizlabs' . DS . 'php_codesniffer';\nif (!is_dir($composerVendorDir . DS . $codesnifferDir)) {\n\t$ideHelperDir = substr(__DIR__, 0, (int)strpos(__DIR__, DS . 'cakephp-ide-helper'));\n\t$composerVendorDir = dirname($ideHelperDir);\n}\n$manualAutoload = $composerVendorDir . DS . $codesnifferDir . DS . 'autoload.php';\nif (!class_exists(Config::class) && file_exists($manualAutoload)) {\n\trequire $manualAutoload;\n}\n\n/**\n * Reads the annotated properties of an entity and adds the constants based on those.\n */\nabstract class AbstractTask {\n\n\tuse FileTrait;\n\tuse InstanceConfigTrait;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_DRY_RUN = 'dry-run';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_PLUGIN = 'plugin';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_NAMESPACE = 'namespace';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_VERBOSE = 'verbose';\n\n\t/**\n\t * @var string\n\t */\n\tpublic const COUNT_ADDED = 'added';\n\n\t/**\n\t * @var array<string, mixed>\n\t */\n\tprotected array $_defaultConfig = [];\n\n\t/**\n\t * @param array<string, mixed> $config\n\t */\n\tpublic function __construct(array $config) {\n\t\t$this->setConfig($config);\n\n\t\t$namespace = $this->getConfig(static::CONFIG_PLUGIN) ?: Configure::read('App.namespace', 'App');\n\t\t$namespace = str_replace('/', '\\\\', $namespace);\n\t\t$this->setConfig(static::CONFIG_NAMESPACE, $namespace);\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tabstract public function shouldRun(string $path): bool;\n\n\t/**\n\t * @param string $content\n\t * @param string $path\n\t * @return string\n\t */\n\tabstract public function run(string $content, string $path): string;\n\n}\n"
  },
  {
    "path": "src/Illuminator/Task/ControllerDefaultTableTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Illuminator\\Task;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotator\\Traits\\DocBlockTrait;\nuse IdeHelper\\Annotator\\Traits\\FileTrait;\nuse PHP_CodeSniffer\\Files\\File;\n\n/**\n * Adds $defaultTable = '' property to controllers that don't have a corresponding table class.\n */\nclass ControllerDefaultTableTask extends AbstractTask {\n\n\tuse FileTrait;\n\tuse DocBlockTrait;\n\n\t/**\n\t * @var array<string, mixed>\n\t */\n\tprotected array $_defaultConfig = [\n\t\t'fastPropertyCheck' => false,\n\t];\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path): bool {\n\t\t$className = pathinfo($path, PATHINFO_FILENAME);\n\t\tif (!str_ends_with($className, 'Controller')) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ($className === 'AppController' || preg_match('#[A-Za-z0-9]AppController$#', $className)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!preg_match('#[\\\\\\/]Controller[\\\\\\/]#', $path)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param string $path Path to file.\n\t * @return string\n\t */\n\tpublic function run(string $content, string $path): string {\n\t\t$file = $this->getFile('', $content);\n\t\t$classIndex = $file->findNext(T_CLASS, 0);\n\t\tif (!$classIndex) {\n\t\t\treturn $content;\n\t\t}\n\n\t\tif ($this->hasDefaultTableProperty($file, $classIndex, $content)) {\n\t\t\treturn $content;\n\t\t}\n\n\t\t$className = pathinfo($path, PATHINFO_FILENAME);\n\t\t$namespace = $this->extractNamespace($content);\n\t\tif (!$namespace) {\n\t\t\treturn $content;\n\t\t}\n\n\t\t$baseNamespace = $this->detectPluginFromNamespace($namespace);\n\n\t\t$modelName = substr($className, 0, -10);\n\t\t$modelClassName = str_replace('/', '\\\\', $baseNamespace) . '\\\\Model\\\\Table\\\\' . $modelName . 'Table';\n\n\t\tif (class_exists($modelClassName)) {\n\t\t\treturn $content;\n\t\t}\n\n\t\treturn $this->addDefaultTableProperty($file, $classIndex, $content);\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $classIndex\n\t * @param string $content\n\t * @return bool\n\t */\n\tprotected function hasDefaultTableProperty(File $file, int $classIndex, string $content): bool {\n\t\t// Fast regex check for performance (optional, can be enabled via config)\n\t\tif ($this->getConfig('fastPropertyCheck')) {\n\t\t\tif (preg_match('/\\bprotected \\?string \\$defaultTable\\b/', $content)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t}\n\n\t\t// Robust token-based check within class scope\n\t\t$tokens = $file->getTokens();\n\n\t\t$openBraceIndex = $tokens[$classIndex]['scope_opener'];\n\t\t$closeBraceIndex = $tokens[$classIndex]['scope_closer'];\n\t\tif (!$openBraceIndex || !$closeBraceIndex) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor ($i = $openBraceIndex + 1; $i < $closeBraceIndex; $i++) {\n\t\t\tif ($tokens[$i]['code'] !== T_VARIABLE) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($tokens[$i]['content'] === '$defaultTable') {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $classIndex\n\t * @param string $content\n\t * @return string\n\t */\n\tprotected function addDefaultTableProperty(File $file, int $classIndex, string $content): string {\n\t\t$tokens = $file->getTokens();\n\n\t\t$openBraceIndex = $tokens[$classIndex]['scope_opener'];\n\t\t$closeBraceIndex = $tokens[$classIndex]['scope_closer'];\n\t\tif (!$openBraceIndex || !$closeBraceIndex) {\n\t\t\treturn $content;\n\t\t}\n\n\t\t$indentation = Configure::read('IdeHelper.illuminatorIndentation') ?? \"\\t\";\n\n\t\t$fixer = $this->getFixer($file);\n\n\t\t$property = PHP_EOL . PHP_EOL . $indentation . 'protected ?string $defaultTable = \\'\\';';\n\n\t\t$fixer->addContent($openBraceIndex, $property);\n\n\t\treturn $fixer->getContents();\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index\n\t * @return string\n\t */\n\tprotected function detectIndentation(File $file, int $index): string {\n\t\t$tokens = $file->getTokens();\n\n\t\t$line = $tokens[$index]['line'];\n\t\t$firstOfLine = $index;\n\t\twhile ($firstOfLine - 1 >= 0 && $tokens[$firstOfLine - 1]['line'] === $line) {\n\t\t\t$firstOfLine--;\n\t\t}\n\n\t\t$whitespace = '';\n\t\tfor ($i = $firstOfLine; $i < $index; $i++) {\n\t\t\tif ($tokens[$i]['code'] === T_WHITESPACE) {\n\t\t\t\t$whitespace .= $tokens[$i]['content'];\n\t\t\t}\n\t\t}\n\n\t\treturn $whitespace;\n\t}\n\n\t/**\n\t * @param string $content\n\t * @return string|null\n\t */\n\tprotected function extractNamespace(string $content): ?string {\n\t\tif (preg_match('/^namespace\\s+([^;]+);/m', $content, $matches)) {\n\t\t\treturn $matches[1];\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param string $namespace\n\t * @return string\n\t */\n\tprotected function detectPluginFromNamespace(string $namespace): string {\n\t\t$plugin = $this->getConfig(static::CONFIG_PLUGIN);\n\t\tif ($plugin) {\n\t\t\treturn $plugin;\n\t\t}\n\n\t\t$parts = explode('\\\\', $namespace);\n\t\t$controllerIndex = array_search('Controller', $parts, true);\n\t\tif ($controllerIndex !== false && $controllerIndex > 0) {\n\t\t\treturn implode('\\\\', array_slice($parts, 0, $controllerIndex));\n\t\t}\n\n\t\treturn Configure::read('App.namespace', 'App');\n\t}\n\n}\n"
  },
  {
    "path": "src/Illuminator/Task/EntityFieldTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Illuminator\\Task;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Utility\\Inflector;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse IdeHelper\\Annotation\\PropertyReadAnnotation;\nuse IdeHelper\\Annotator\\Traits\\DocBlockTrait;\nuse IdeHelper\\Annotator\\Traits\\FileTrait;\nuse PHP_CodeSniffer\\Files\\File;\nuse PHP_CodeSniffer\\Util\\Tokens;\n\n/**\n * Reads the annotated properties of an entity and adds the constants based on those.\n */\nclass EntityFieldTask extends AbstractTask {\n\n\tuse FileTrait;\n\tuse DocBlockTrait;\n\n\t/**\n\t * @var string\n\t */\n\tpublic const PREFIX = 'FIELD_';\n\n\t/**\n\t * @var array<string, mixed>\n\t */\n\tprotected array $_defaultConfig = [\n\t\t'visibility' => null,\n\t];\n\n\t/**\n\t * @var bool|null\n\t */\n\tprotected $_visibility;\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path): bool {\n\t\treturn (bool)preg_match('#' . preg_quote(DS) . 'Model' . preg_quote(DS) . 'Entity' . preg_quote(DS) . '.+\\\\.php$#', $path);\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param string $path Path to file.\n\t * @return string\n\t */\n\tpublic function run(string $content, string $path): string {\n\t\t$file = $this->getFile('', $content);\n\n\t\t$classIndex = $file->findNext(T_CLASS, 0);\n\t\tif (!$classIndex) {\n\t\t\treturn $content;\n\t\t}\n\n\t\t$tokens = $file->getTokens();\n\n\t\t$fields = $this->getFields($file, $classIndex);\n\n\t\t$existingConstants = $this->getFieldConstants($tokens, $tokens[$classIndex]['scope_opener'], $tokens[$classIndex]['scope_closer']);\n\t\tif ($existingConstants) {\n\t\t\t$fields = array_diff_key($fields, $existingConstants);\n\t\t\t$existingConstant = array_pop($existingConstants);\n\t\t\t$index = $existingConstant['index'];\n\t\t\t$addToExisting = true;\n\t\t} else {\n\t\t\t$index = $file->findPrevious(T_WHITESPACE, $tokens[$classIndex]['scope_closer'] + -1, $tokens[$classIndex]['scope_opener'], true);\n\t\t\tif ($index === false) {\n\t\t\t\t$index = $tokens[$classIndex]['scope_opener'];\n\t\t\t}\n\t\t\t$addToExisting = false;\n\t\t}\n\n\t\treturn $this->addClassConstants($file, $fields, $index, $addToExisting, 0) ?: $content;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $classIndex\n\t * @return array<string, array<string, mixed>>\n\t */\n\tprotected function getFields(File $file, int $classIndex): array {\n\t\t$tokens = $file->getTokens();\n\n\t\t$docBlockCloseTagIndex = $this->findDocBlockCloseTagIndex($file, $classIndex);\n\t\tif (!$docBlockCloseTagIndex || empty($tokens[$docBlockCloseTagIndex]['comment_opener'])) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$fields = [];\n\t\tfor ($i = $tokens[$docBlockCloseTagIndex]['comment_opener'] + 1; $i < $docBlockCloseTagIndex; $i++) {\n\n\t\t\tif ($tokens[$i]['code'] !== T_DOC_COMMENT_TAG) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif ($tokens[$i]['content'] !== PropertyAnnotation::TAG && $tokens[$i]['content'] !== PropertyReadAnnotation::TAG) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t/** @var \\PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PropertyTagValueNode $valueNode */\n\t\t\t$valueNode = static::getValueNode($tokens[$i]['content'], $tokens[$i + 2]['content']);\n\t\t\t$returnTypes = $this->valueNodeParts($valueNode);\n\t\t\t$typeString = $this->renderUnionTypes($returnTypes);\n\n\t\t\t$varAndComment = substr($tokens[$i + 2]['content'], strlen($typeString) + 1);\n\t\t\t$varName = mb_substr($varAndComment, 1);\n\t\t\t$pieces = explode(' ', $varName);\n\t\t\t$field = $pieces[0];\n\n\t\t\tif (str_starts_with($field, '_')) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// We also skip camelCase as those are not the convention\n\t\t\tif (Inflector::underscore($field) !== $field) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$fields[$field] = [\n\t\t\t\t'name' => $field,\n\t\t\t\t'constant' => static::PREFIX . mb_strtoupper($field),\n\t\t\t\t'index' => $i,\n\t\t\t];\n\t\t}\n\n\t\treturn $fields;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param int $index First functional code after docblock\n\t *\n\t * @return int|null\n\t */\n\tprotected function findDocBlockCloseTagIndex(File $file, int $index): ?int {\n\t\t$prevCode = $file->findPrevious(Tokens::$emptyTokens, $index - 1, null, true);\n\t\tif (!$prevCode) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn $file->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $index - 1, $prevCode) ?: null;\n\t}\n\n\t/**\n\t * @param \\PHP_CodeSniffer\\Files\\File $file\n\t * @param array<array<string, mixed>> $fields\n\t * @param int $index Index of first token of previous line\n\t * @param bool $addToExisting\n\t * @param int $level\n\t * @return string|null\n\t */\n\tprotected function addClassConstants(File $file, array $fields, $index, $addToExisting, $level = 1) {\n\t\tif (!$fields) {\n\t\t\treturn null;\n\t\t}\n\n\t\t$tokens = $file->getTokens();\n\n\t\t$line = $tokens[$index]['line'];\n\n\t\t$i = $index;\n\t\twhile ($tokens[$i + 1]['line'] === $line) {\n\t\t\t$i++;\n\t\t}\n\n\t\t$lastTokenOfLastLine = $i;\n\n\t\t$whitespace = '';\n\t\t$firstOfLine = $index;\n\t\twhile ($tokens[$firstOfLine - 1]['line'] === $tokens[$index]['line']) {\n\t\t\t$firstOfLine--;\n\t\t\t$whitespace .= $tokens[$firstOfLine]['content'];\n\t\t}\n\t\tif ($level < 1) {\n\t\t\t$whitespace = Configure::read('IdeHelper.illuminatorIndentation') ?? \"\\t\";\n\t\t}\n\n\t\t$beginIndex = $lastTokenOfLastLine;\n\t\t$visibility = '';\n\t\tif ($this->visibility()) {\n\t\t\t$visibility = 'public ';\n\t\t}\n\n\t\t$fixer = $this->getFixer($file);\n\n\t\t$fixer->beginChangeset();\n\n\t\tif (!$addToExisting) {\n\t\t\t$fixer->addNewline($beginIndex);\n\t\t}\n\n\t\tforeach ($fields as $field) {\n\t\t\t$fixer->addContent($beginIndex, $whitespace . $visibility . 'const ' . $field['constant'] . ' = \\'' . $field['name'] . '\\';');\n\t\t\t$fixer->addNewline($beginIndex);\n\t\t}\n\n\t\t$fixer->endChangeset();\n\n\t\treturn $fixer->getContents();\n\t}\n\n\t/**\n\t * @param array<array<string, mixed>> $tokens\n\t * @param int $startIndex\n\t * @param int $endIndex\n\t * @return array<string, array<string, mixed>>\n\t */\n\tprotected function getFieldConstants(array $tokens, int $startIndex, int $endIndex) {\n\t\t$constants = [];\n\n\t\tfor ($i = $startIndex + 1; $i < $endIndex; $i++) {\n\t\t\tif ($tokens[$i]['code'] !== T_CONST) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$index = $i + 1;\n\t\t\tif ($tokens[$index]['code'] === T_WHITESPACE) {\n\t\t\t\t$index++;\n\t\t\t}\n\t\t\tif ($tokens[$index]['code'] !== T_STRING) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$constant = $tokens[$index]['content'];\n\n\t\t\t$pos = strpos($constant, '_') ?: 0;\n\t\t\t$prefix = substr($constant, 0, $pos) ?: '';\n\t\t\tif ($prefix . '_' !== static::PREFIX) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$field = substr($constant, $pos + 1);\n\t\t\t$field = strtolower($field);\n\n\t\t\t$constants[$field] = [\n\t\t\t\t'index' => $i,\n\t\t\t\t'prefix' => $prefix,\n\t\t\t\t'name' => $field,\n\t\t\t\t'constant' => $constant,\n\t\t\t];\n\t\t}\n\n\t\treturn $constants;\n\t}\n\n\t/**\n\t * If visibility \"public\" should be used, for PHP 7.1+ only.\n\t *\n\t * @return bool\n\t */\n\tprotected function visibility(): bool {\n\t\tif ($this->_visibility !== null) {\n\t\t\treturn $this->_visibility;\n\t\t}\n\n\t\t$visConfig = $this->getConfig('visibility') ?? true;\n\n\t\treturn $this->_visibility = $visConfig;\n\t}\n\n}\n"
  },
  {
    "path": "src/Illuminator/Task/TableValidationLinkTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Illuminator\\Task;\n\nuse PhpParser\\Node;\nuse PhpParser\\NodeTraverser;\nuse PhpParser\\NodeVisitorAbstract;\nuse PhpParser\\ParserFactory;\nuse Throwable;\n\n/**\n * Adds @link annotations above 'rule' definitions that reference table provider methods.\n *\n * When validation rules use 'provider' => 'table' with a method reference,\n * this task adds a @link annotation directly above the 'rule' line to help\n * IDEs recognize the method usage.\n */\nclass TableValidationLinkTask extends AbstractTask {\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path): bool {\n\t\t$className = pathinfo($path, PATHINFO_FILENAME);\n\n\t\treturn $className !== 'Table' && str_ends_with($className, 'Table');\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param string $path Path to file.\n\t * @return string\n\t */\n\tpublic function run(string $content, string $path): string {\n\t\t$links = $this->findTableProviderMethods($content);\n\t\tif (!$links) {\n\t\t\treturn $content;\n\t\t}\n\n\t\treturn $this->insertLinkAnnotations($content, $links);\n\t}\n\n\t/**\n\t * Find all ->add() calls with 'provider' => 'table' and extract the method names.\n\t *\n\t * @param string $content\n\t * @return array<int, string> Map of line number (of 'rule' key) => method name\n\t */\n\tprotected function findTableProviderMethods(string $content): array {\n\t\t$parser = (new ParserFactory())->createForNewestSupportedVersion();\n\n\t\ttry {\n\t\t\t$ast = $parser->parse($content);\n\t\t} catch (Throwable $e) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif ($ast === null) {\n\t\t\treturn [];\n\t\t}\n\n\t\t$visitor = new class extends NodeVisitorAbstract {\n\t\t\t/**\n\t\t\t * @var array<int, string>\n\t\t\t */\n\t\t\tpublic array $links = [];\n\n\t\t\t/**\n\t\t\t * @param \\PhpParser\\Node $node\n\t\t\t * @return int|null\n\t\t\t */\n\t\t\tpublic function enterNode(Node $node): ?int {\n\t\t\t\tif (!$node instanceof Node\\Expr\\MethodCall) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\tif (!$node->name instanceof Node\\Identifier || $node->name->name !== 'add') {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\t// Need at least 3 arguments: field, ruleName, options array\n\t\t\t\tif (count($node->args) < 3) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\t$optionsArg = $node->args[2];\n\t\t\t\tif (!$optionsArg instanceof Node\\Arg) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\t$options = $optionsArg->value;\n\t\t\t\tif (!$options instanceof Node\\Expr\\Array_) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\n\t\t\t\t$result = $this->extractTableProviderMethod($options);\n\t\t\t\tif ($result !== null) {\n\t\t\t\t\t$this->links[$result['ruleLine']] = $result['methodName'];\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @param \\PhpParser\\Node\\Expr\\Array_ $options\n\t\t\t * @return array{ruleLine: int, methodName: string}|null\n\t\t\t */\n\t\t\tprotected function extractTableProviderMethod(Node\\Expr\\Array_ $options): ?array {\n\t\t\t\t$hasTableProvider = false;\n\t\t\t\t$methodName = null;\n\t\t\t\t$ruleLine = null;\n\n\t\t\t\tforeach ($options->items as $item) {\n\t\t\t\t\tif (!$item instanceof Node\\Expr\\ArrayItem || $item->key === null) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t$key = $this->getStringValue($item->key);\n\t\t\t\t\tif ($key === null) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif ($key === 'provider') {\n\t\t\t\t\t\t$value = $this->getStringValue($item->value);\n\t\t\t\t\t\tif ($value === 'table') {\n\t\t\t\t\t\t\t$hasTableProvider = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif ($key === 'rule') {\n\t\t\t\t\t\t$methodName = $this->extractMethodFromRule($item->value);\n\t\t\t\t\t\t$ruleLine = $item->getStartLine();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ($hasTableProvider && $methodName !== null && $ruleLine !== null) {\n\t\t\t\t\treturn ['ruleLine' => $ruleLine, 'methodName' => $methodName];\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @param \\PhpParser\\Node\\Expr $value\n\t\t\t * @return string|null\n\t\t\t */\n\t\t\tprotected function extractMethodFromRule(Node\\Expr $value): ?string {\n\t\t\t\t// 'rule' => 'methodName'\n\t\t\t\t$stringValue = $this->getStringValue($value);\n\t\t\t\tif ($stringValue !== null) {\n\t\t\t\t\treturn $stringValue;\n\t\t\t\t}\n\n\t\t\t\t// 'rule' => ['methodName', 'arg1', ...]\n\t\t\t\tif ($value instanceof Node\\Expr\\Array_ && count($value->items) > 0) {\n\t\t\t\t\t$firstItem = $value->items[0];\n\t\t\t\t\tif ($firstItem instanceof Node\\Expr\\ArrayItem) {\n\t\t\t\t\t\treturn $this->getStringValue($firstItem->value);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t/**\n\t\t\t * @param \\PhpParser\\Node\\Expr $expr\n\t\t\t * @return string|null\n\t\t\t */\n\t\t\tprotected function getStringValue(Node\\Expr $expr): ?string {\n\t\t\t\tif ($expr instanceof Node\\Scalar\\String_) {\n\t\t\t\t\treturn $expr->value;\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\t\t\t}\n\t\t};\n\n\t\t$traverser = new NodeTraverser();\n\t\t$traverser->addVisitor($visitor);\n\t\t$traverser->traverse($ast);\n\n\t\treturn $visitor->links;\n\t}\n\n\t/**\n\t * Insert @link annotations above the specified lines.\n\t *\n\t * @param string $content\n\t * @param array<int, string> $links Map of line number => method name\n\t * @return string\n\t */\n\tprotected function insertLinkAnnotations(string $content, array $links): string {\n\t\t$lines = explode(\"\\n\", $content);\n\n\t\t// Sort by line number descending to avoid offset issues when inserting\n\t\tkrsort($links);\n\n\t\tforeach ($links as $lineNumber => $methodName) {\n\t\t\t$index = $lineNumber - 1; // Convert to 0-based index\n\t\t\tif (!isset($lines[$index])) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Check if there's already a @link annotation for this method\n\t\t\tif ($index > 0 && $this->hasLinkAnnotation($lines[$index - 1], $methodName)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Get the indentation of the target line\n\t\t\t$targetLine = $lines[$index];\n\t\t\tpreg_match('/^(\\s*)/', $targetLine, $matches);\n\t\t\t$indent = $matches[1] ?? '';\n\n\t\t\t// Insert the @link annotation\n\t\t\t$annotation = $indent . '/** @link ' . $methodName . '() */';\n\t\t\tarray_splice($lines, $index, 0, [$annotation]);\n\t\t}\n\n\t\treturn implode(\"\\n\", $lines);\n\t}\n\n\t/**\n\t * Check if a line already contains a @link annotation for the given method.\n\t *\n\t * @param string $line\n\t * @param string $methodName\n\t * @return bool\n\t */\n\tprotected function hasLinkAnnotation(string $line, string $methodName): bool {\n\t\treturn str_contains($line, '@link') && str_contains($line, $methodName);\n\t}\n\n}\n"
  },
  {
    "path": "src/Illuminator/TaskCollection.php",
    "content": "<?php\n\nnamespace IdeHelper\\Illuminator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\Illuminator\\Task\\AbstractTask;\nuse IdeHelper\\Illuminator\\Task\\ControllerDefaultTableTask;\nuse IdeHelper\\Illuminator\\Task\\EntityFieldTask;\nuse IdeHelper\\Illuminator\\Task\\TableValidationLinkTask;\nuse InvalidArgumentException;\nuse RuntimeException;\nuse SebastianBergmann\\Diff\\Differ;\nuse SebastianBergmann\\Diff\\Output\\DiffOnlyOutputBuilder;\n\nclass TaskCollection {\n\n\t/**\n\t * @var string\n\t */\n\tpublic const CONFIG_DRY_RUN = 'dry-run';\n\n\tprotected Io $_io;\n\n\t/**\n\t * @var array<string, mixed>\n\t */\n\tprotected array $_config;\n\n\t/**\n\t * @var array<class-string<\\IdeHelper\\Illuminator\\Task\\AbstractTask>, class-string<\\IdeHelper\\Illuminator\\Task\\AbstractTask>>\n\t */\n\tprotected array $defaultTasks = [\n\t\tControllerDefaultTableTask::class => ControllerDefaultTableTask::class,\n\t\tEntityFieldTask::class => EntityFieldTask::class,\n\t\tTableValidationLinkTask::class => TableValidationLinkTask::class,\n\t];\n\n\t/**\n\t * @var array<\\IdeHelper\\Illuminator\\Task\\AbstractTask>\n\t */\n\tprotected array $tasks = [];\n\n\t/**\n\t * @param \\IdeHelper\\Console\\Io $io\n\t * @param array<string, mixed> $config\n\t * @param array<string> $tasks\n\t * @throws \\InvalidArgumentException\n\t */\n\tpublic function __construct(Io $io, array $config, array $tasks = []) {\n\t\t$this->_io = $io;\n\t\t$this->_config = $config;\n\n\t\t$defaultTasks = $this->defaultTasks();\n\n\t\t$keyMap = $this->taskNames($defaultTasks);\n\t\t$filterMap = array_diff($tasks, $keyMap);\n\t\tif ($filterMap) {\n\t\t\tthrow new InvalidArgumentException('Tasks do not exist: ' . implode(', ', $filterMap) . '.');\n\t\t}\n\n\t\tforeach ($defaultTasks as $key => $task) {\n\t\t\tif (!$task) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t$lookupKey = $keyMap[$key];\n\t\t\tif ($tasks && !in_array($lookupKey, $tasks, true)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$this->add($task);\n\t\t}\n\t}\n\n\t/**\n\t * @return array<class-string<\\IdeHelper\\Illuminator\\Task\\AbstractTask>>\n\t */\n\tprotected function defaultTasks(): array {\n\t\t$tasks = (array)Configure::read('IdeHelper.illuminatorTasks') + $this->defaultTasks;\n\n\t\tforeach ($tasks as $k => $v) {\n\t\t\tif (is_numeric($k)) {\n\t\t\t\t$tasks[$v] = $v;\n\t\t\t\tunset($tasks[$k]);\n\t\t\t}\n\t\t}\n\n\t\treturn $tasks;\n\t}\n\n\t/**\n\t * Adds a task to the collection.\n\t *\n\t * @param \\IdeHelper\\Illuminator\\Task\\AbstractTask|string $task The task to map.\n\t * @throws \\InvalidArgumentException\n\t * @return $this\n\t */\n\tprotected function add($task) {\n\t\tif (is_string($task)) {\n\t\t\t$task = new $task($this->_config);\n\t\t}\n\n\t\t$class = get_class($task);\n\t\tif (!$task instanceof AbstractTask) {\n\t\t\tthrow new InvalidArgumentException(\n\t\t\t\tsprintf('Cannot use `%s` as task, it is not implementing `%s`.', $class, AbstractTask::class),\n\t\t\t);\n\t\t}\n\n\t\t$this->tasks[$class] = $task;\n\n\t\treturn $this;\n\t}\n\n\t/**\n\t * @return array<\\IdeHelper\\Illuminator\\Task\\AbstractTask>\n\t */\n\tpublic function tasks(): array {\n\t\treturn $this->tasks;\n\t}\n\n\t/**\n\t * @param array<string>|array<\\IdeHelper\\Illuminator\\Task\\AbstractTask> $tasks\n\t * @throws \\RuntimeException\n\t * @return array<string>\n\t */\n\tpublic function taskNames($tasks = []): array {\n\t\tif (!$tasks) {\n\t\t\t$tasks = $this->tasks;\n\t\t}\n\n\t\t$keys = array_keys($tasks);\n\t\t$keyMap = array_combine($keys, $keys) ?: [];\n\t\tforeach ($keyMap as $k => $v) {\n\t\t\tpreg_match('#\\bTask\\\\\\\\([A-Za-z0-9]+)Task$#', $v, $matches);\n\t\t\tif (!$matches) {\n\t\t\t\tthrow new RuntimeException('Invalid task name: ' . $v);\n\t\t\t}\n\t\t\t$keyMap[$k] = $matches[1];\n\t\t}\n\n\t\treturn $keyMap;\n\t}\n\n\t/**\n\t * @param string $path File path\n\t * @return bool True if file is/was modified; false if nothing changed.\n\t */\n\tpublic function run(string $path): bool {\n\t\t$file = str_replace(ROOT . DS, DS, $path);\n\t\t$this->_io->verbose('# ' . $file);\n\n\t\t$content = $result = null;\n\n\t\tforeach ($this->tasks as $task) {\n\t\t\tif (!$task->shouldRun($path)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($content === null) {\n\t\t\t\t$content = file_get_contents($path);\n\t\t\t\tif ($content === false) {\n\t\t\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t\t\t}\n\t\t\t\t$result = $content;\n\t\t\t}\n\n\t\t\t$result = $task->run((string)$result, $path);\n\t\t}\n\n\t\tif ($content === null || $result === $content) {\n\t\t\treturn false;\n\t\t}\n\n\t\t$this->displayDiff($content, (string)$result);\n\t\t$this->storeFile($path, (string)$result, $this->_config[static::CONFIG_DRY_RUN]);\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $oldContent\n\t * @param string $newContent\n\t * @return void\n\t */\n\tprotected function displayDiff(string $oldContent, string $newContent): void {\n\t\t$differ = new Differ(new DiffOnlyOutputBuilder());\n\t\t$array = $differ->diffToArray($oldContent, $newContent);\n\n\t\t$begin = null;\n\t\t$end = null;\n\t\t/**\n\t\t * @var int $key\n\t\t */\n\t\tforeach ($array as $key => $row) {\n\t\t\tif ($row[1] === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($begin === null) {\n\t\t\t\t$begin = $key;\n\t\t\t}\n\t\t\t$end = $key;\n\t\t}\n\t\tif ($begin === null) {\n\t\t\treturn;\n\t\t}\n\t\t$firstLineOfOutput = $begin > 0 ? $begin - 1 : 0;\n\t\t$lastLineOfOutput = count($array) - 1 > $end ? $end + 1 : $end;\n\n\t\tfor ($i = $firstLineOfOutput; $i <= $lastLineOfOutput; $i++) {\n\t\t\t$row = $array[$i];\n\n\t\t\t$char = ' ';\n\t\t\t$output = trim($row[0], \"\\n\\r\\0\\x0B\");\n\n\t\t\tif ($row[1] === 1) {\n\t\t\t\t$char = '+';\n\t\t\t\t$this->_io->info('   | ' . $char . $output, 1, ConsoleIo::VERBOSE);\n\t\t\t} elseif ($row[1] === 2) {\n\t\t\t\t$char = '-';\n\t\t\t\t$this->_io->out('<warning>' . '   | ' . $char . $output . '</warning>', 1, ConsoleIo::VERBOSE);\n\t\t\t} else {\n\t\t\t\t$this->_io->out('   | ' . $char . $output, 1, ConsoleIo::VERBOSE);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $contents\n\t * @param bool $dryRun\n\t * @throws \\RuntimeException When the file cannot be written.\n\t * @return void\n\t */\n\tprotected function storeFile(string $path, string $contents, bool $dryRun): void {\n\t\tif ($dryRun) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (file_put_contents($path, $contents) === false) {\n\t\t\tthrow new RuntimeException(sprintf('Failed to write file `%s`.', $path));\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "src/Utility/App.php",
    "content": "<?php\n\nnamespace IdeHelper\\Utility;\n\nuse Cake\\Core\\App as CoreApp;\nuse Cake\\Core\\Configure;\nuse Cake\\Http\\Exception\\NotFoundException;\nuse Throwable;\n\nclass App extends CoreApp {\n\n\t/**\n\t * @param string $class\n\t * @param string $type\n\t * @param string $suffix\n\t * @param bool $includeApp\n\t *\n\t * @return string|null\n\t */\n\tpublic static function className(string $class, string $type = '', string $suffix = '', bool $includeApp = true): ?string {\n\t\ttry {\n\t\t\tif (str_contains($class, '\\\\')) {\n\t\t\t\treturn class_exists($class) ? $class : null;\n\t\t\t}\n\n\t\t\t[$plugin, $name] = pluginSplit($class);\n\t\t\t$fullName = '\\\\' . str_replace('/', '\\\\', $type . '\\\\' . $name) . $suffix;\n\n\t\t\t$appNamespace = $includeApp ? Configure::read('App.namespace') : null;\n\t\t\t$base = $plugin ?: $appNamespace;\n\t\t\tif ($base !== null) {\n\t\t\t\t$base = str_replace('/', '\\\\', rtrim($base, '\\\\'));\n\n\t\t\t\tif (static::_classExistsInBase($fullName, $base)) {\n\t\t\t\t\t/** @var class-string */\n\t\t\t\t\treturn $base . $fullName;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ($plugin || !static::_classExistsInBase($fullName, 'Cake')) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t/** @var class-string */\n\t\t\treturn 'Cake' . $fullName;\n\t\t} catch (Throwable $e) {\n\t\t\t// Do nothing\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * @param string $class\n\t * @param string $type\n\t * @param string $suffix\n\t *\n\t * @throws \\Cake\\Http\\Exception\\NotFoundException\n\t *\n\t * @return string\n\t */\n\tpublic static function classNameOrFail(string $class, string $type = '', string $suffix = ''): string {\n\t\t$className = parent::className($class, $type, $suffix);\n\t\tif (!$className) {\n\t\t\tthrow new NotFoundException('Class not found.');\n\t\t}\n\n\t\treturn $className;\n\t}\n\n}\n"
  },
  {
    "path": "src/Utility/AppPath.php",
    "content": "<?php\n\nnamespace IdeHelper\\Utility;\n\nuse Cake\\Core\\App;\nuse Cake\\Core\\Exception\\MissingPluginException;\n\nclass AppPath {\n\n\t/**\n\t * @param string $type\n\t * @param string|null $plugin\n\t * @throws \\Cake\\Core\\Exception\\MissingPluginException\n\t * @return array<string>\n\t */\n\tpublic static function get(string $type, ?string $plugin = null): array {\n\t\ttry {\n\t\t\treturn App::classPath($type, $plugin);\n\t\t} catch (MissingPluginException $exception) {\n\t\t}\n\n\t\treturn App::classPath($type, $plugin);\n\t}\n\n}\n"
  },
  {
    "path": "src/Utility/CollectionClass.php",
    "content": "<?php\n\nnamespace IdeHelper\\Utility;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Core\\Exception\\CakeException;\n\nclass CollectionClass {\n\n\t/**\n\t * @param string $name FQCN\n\t *\n\t * @return string\n\t */\n\tpublic static function name(string $name): string {\n\t\t$configured = Configure::read('IdeHelper.templateCollectionObject');\n\t\tif ($configured === null || $configured === true) {\n\t\t\treturn $name;\n\t\t}\n\t\tif ($configured === false) {\n\t\t\treturn 'array';\n\t\t}\n\t\tif (!is_string($configured)) {\n\t\t\tthrow new CakeException('`templateCollectionObject` config must be `string|bool|null`.');\n\t\t}\n\n\t\treturn $configured;\n\t}\n\n}\n"
  },
  {
    "path": "src/Utility/ControllerActionParser.php",
    "content": "<?php\n\nnamespace IdeHelper\\Utility;\n\nuse App\\Controller\\AppController;\nuse ReflectionClass;\nuse ReflectionMethod;\nuse RuntimeException;\nuse Throwable;\n\nclass ControllerActionParser {\n\n\t/**\n\t * @var array<string>|null\n\t */\n\tprotected static $appControllerActions;\n\n\t/**\n\t * @param string $path\n\t *\n\t * @return array<string>\n\t */\n\tpublic function parse(string $path): array {\n\t\t$actions = $this->parseFile($path);\n\n\t\tif (static::$appControllerActions === null) {\n\t\t\ttry {\n\t\t\t\t$class = new ReflectionClass(AppController::class);\n\t\t\t\t$methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);\n\t\t\t} catch (Throwable $exception) {\n\t\t\t\treturn [];\n\t\t\t}\n\n\t\t\tstatic::$appControllerActions = [];\n\t\t\tforeach ($methods as $method) {\n\t\t\t\tstatic::$appControllerActions[] = $method->getName();\n\t\t\t}\n\t\t}\n\n\t\t$actions = array_diff($actions, static::$appControllerActions);\n\n\t\treturn array_values($actions);\n\t}\n\n\t/**\n\t * @param string $path\n\t *\n\t * @return array<string>\n\t */\n\tprotected function parseFile($path): array {\n\t\t$content = file_get_contents($path);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\n\t\tpreg_match_all('/public function (.+)\\(/', $content, $matches);\n\t\tif (empty($matches[1])) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn $matches[1];\n\t}\n\n}\n"
  },
  {
    "path": "src/Utility/GenericString.php",
    "content": "<?php\n\nnamespace IdeHelper\\Utility;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Datasource\\ResultSetInterface;\n\nclass GenericString {\n\n\t/**\n\t * @param string $value\n\t * @param string|null $type\n\t *\n\t * @return string\n\t */\n\tpublic static function generate(string $value, ?string $type = null): string {\n\t\tif ($type !== null && str_starts_with($type, '\\\\')) {\n\t\t\t$typeCheck = substr($type, 1);\n\t\t} else {\n\t\t\t$typeCheck = $type;\n\t\t}\n\n\t\t$detailed = Configure::read('IdeHelper.genericsInParam') === 'detailed';\n\t\tif ($detailed && $typeCheck === ResultSetInterface::class) {\n\t\t\treturn sprintf($type . '<int, %s>', $value);\n\t\t}\n\n\t\tif (Configure::read('IdeHelper.arrayAsGenerics') && ($type === null || in_array($type, ['array', 'iterable'], true))) {\n\t\t\treturn sprintf(($type ?: 'array' ) . '<%s>', $value);\n\t\t}\n\t\tif (Configure::read('IdeHelper.objectAsGenerics') && $type !== null) {\n\t\t\treturn sprintf($type . '<%s>', $value);\n\t\t}\n\n\t\tif ($typeCheck === ResultSetInterface::class) {\n\t\t\tif (Configure::read('IdeHelper.concreteEntitiesInParam')) {\n\t\t\t\treturn sprintf($type . '<%s>', $value);\n\t\t\t}\n\n\t\t\treturn $value . '[]|' . $type . '<' . $value . '>';\n\t\t}\n\n\t\t$value .= '[]';\n\t\tif ($type) {\n\t\t\t$value .= '|' . $type;\n\t\t}\n\n\t\treturn $value;\n\t}\n\n}\n"
  },
  {
    "path": "src/Utility/Plugin.php",
    "content": "<?php\n\nnamespace IdeHelper\\Utility;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Core\\Plugin as CorePlugin;\n\nclass Plugin extends CorePlugin {\n\n\t/**\n\t * @return array<string>\n\t */\n\tpublic static function all(): array {\n\t\t$plugins = static::loaded();\n\t\t$plugins = array_combine($plugins, $plugins);\n\n\t\t$pluginMap = (array)Configure::read('IdeHelper.plugins');\n\t\tforeach ($pluginMap as $plugin) {\n\t\t\tif (str_starts_with($plugin, '-')) {\n\t\t\t\t$plugin = substr($plugin, 1);\n\t\t\t\tunset($plugins[$plugin]);\n\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t$plugins[$plugin] = $plugin;\n\t\t}\n\n\t\treturn $plugins;\n\t}\n\n}\n"
  },
  {
    "path": "src/Utility/PluginPath.php",
    "content": "<?php\n\nnamespace IdeHelper\\Utility;\n\nuse Cake\\Core\\Exception\\MissingPluginException;\n\nclass PluginPath {\n\n\t/**\n\t * @param string $plugin\n\t * @throws \\Cake\\Core\\Exception\\MissingPluginException\n\t * @return string\n\t */\n\tpublic static function get(string $plugin): string {\n\t\ttry {\n\t\t\treturn Plugin::path($plugin);\n\t\t} catch (MissingPluginException $exception) {\n\t\t}\n\n\t\treturn Plugin::path($plugin);\n\t}\n\n\t/**\n\t * @param string $plugin\n\t *\n\t * @return string\n\t */\n\tpublic static function classPath(string $plugin): string {\n\t\ttry {\n\t\t\treturn Plugin::classPath($plugin);\n\t\t} catch (MissingPluginException $exception) {\n\t\t}\n\n\t\treturn Plugin::classPath($plugin);\n\t}\n\n}\n"
  },
  {
    "path": "src/Utility/TranslationParser.php",
    "content": "<?php\n\nnamespace IdeHelper\\Utility;\n\nuse Cake\\I18n\\Parser\\PoFileParser;\n\nclass TranslationParser {\n\n\tprotected PoFileParser $poFileParser;\n\n\tpublic function __construct() {\n\t\t$this->poFileParser = new PoFileParser();\n\t}\n\n\t/**\n\t * @param string $path File path\n\t *\n\t * @return array<string>\n\t */\n\tpublic function parse(string $path): array {\n\t\t$result = $this->poFileParser->parse($path);\n\t\t$resultKeys = array_keys($result);\n\n\t\t$domainKeys = [];\n\t\tforeach ($resultKeys as $resultKey) {\n\t\t\t$resultKey = $this->escapeSlashes($resultKey);\n\n\t\t\t$domainKeys[$resultKey] = $resultKey;\n\t\t}\n\n\t\treturn $domainKeys;\n\t}\n\n\t/**\n\t * @param string $key\n\t *\n\t * @return string\n\t */\n\tprotected function escapeSlashes(string $key): string {\n\t\treturn addcslashes($key, '\\'');\n\t}\n\n}\n"
  },
  {
    "path": "src/ValueObject/ArgumentsSet.php",
    "content": "<?php\n\nnamespace IdeHelper\\ValueObject;\n\n/**\n * Helps to use an existing argument set to be used in other directives for DRY code.\n *\n * Then it can be used in other places as argumentsSet('mySet').\n *\n * @see https://www.jetbrains.com/help/phpstorm/ide-advanced-metadata.html#arguments-set\n */\nclass ArgumentsSet implements ValueObjectInterface {\n\n\tprotected string $value;\n\n\t/**\n\t * @param string $value\n\t */\n\tprivate function __construct(string $value) {\n\t\t$this->value = $value;\n\t}\n\n\t/**\n\t * Creates itself from a string.\n\t *\n\t * @param string $value\n\t *\n\t * @return static\n\t */\n\tpublic static function create(string $value) {\n\t\treturn new static($value);\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function raw(): string {\n\t\treturn $this->value;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function __toString(): string {\n\t\treturn 'argumentsSet(\\'' . $this->value . '\\')';\n\t}\n\n}\n"
  },
  {
    "path": "src/ValueObject/ClassName.php",
    "content": "<?php\n\nnamespace IdeHelper\\ValueObject;\n\n/**\n * Holds a FQCN string, which can be auto-casted to string\n */\nclass ClassName implements ValueObjectInterface {\n\n\tprotected string $className;\n\n\t/**\n\t * @param string $className\n\t */\n\tprivate function __construct(string $className) {\n\t\t$this->className = $className;\n\t}\n\n\t/**\n\t * Creates itself from a fully qualified class name.\n\t *\n\t * @param string $className\n\t * @return static\n\t */\n\tpublic static function create(string $className) {\n\t\tif (str_starts_with($className, '\\\\')) {\n\t\t\t$className = substr($className, 1);\n\t\t}\n\n\t\treturn new static($className);\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function raw(): string {\n\t\treturn $this->className;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function __toString(): string {\n\t\treturn '\\\\' . $this->className . '::class';\n\t}\n\n}\n"
  },
  {
    "path": "src/ValueObject/KeyValue.php",
    "content": "<?php\n\nnamespace IdeHelper\\ValueObject;\n\n/**\n * Holds a ValueObject key/value combination that will be treated literally on output.\n */\nclass KeyValue {\n\n\tprotected ValueObjectInterface $key;\n\n\tprotected ValueObjectInterface $value;\n\n\t/**\n\t * @param \\IdeHelper\\ValueObject\\ValueObjectInterface $key\n\t * @param \\IdeHelper\\ValueObject\\ValueObjectInterface $value\n\t */\n\tprivate function __construct(ValueObjectInterface $key, ValueObjectInterface $value) {\n\t\t$this->key = $key;\n\t\t$this->value = $value;\n\t}\n\n\t/**\n\t * Creates itself from a ValueObjectInterface key and value.\n\t *\n\t * @param \\IdeHelper\\ValueObject\\ValueObjectInterface $key\n\t * @param \\IdeHelper\\ValueObject\\ValueObjectInterface $value\n\t *\n\t * @return static\n\t */\n\tpublic static function create(ValueObjectInterface $key, ValueObjectInterface $value) {\n\t\treturn new static($key, $value);\n\t}\n\n\t/**\n\t * @return \\IdeHelper\\ValueObject\\ValueObjectInterface\n\t */\n\tpublic function key(): ValueObjectInterface {\n\t\treturn $this->key;\n\t}\n\n\t/**\n\t * @return \\IdeHelper\\ValueObject\\ValueObjectInterface\n\t */\n\tpublic function value(): ValueObjectInterface {\n\t\treturn $this->value;\n\t}\n\n}\n"
  },
  {
    "path": "src/ValueObject/LiteralName.php",
    "content": "<?php\n\nnamespace IdeHelper\\ValueObject;\n\n/**\n * Holds a string that will be treated literally on output (no extra quoting).\n */\nclass LiteralName implements ValueObjectInterface {\n\n\tprotected string $value;\n\n\t/**\n\t * @param string $value\n\t */\n\tprivate function __construct(string $value) {\n\t\t$this->value = $value;\n\t}\n\n\t/**\n\t * Creates itself from a string.\n\t *\n\t * @param string $value\n\t *\n\t * @return static\n\t */\n\tpublic static function create(string $value) {\n\t\treturn new static($value);\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function raw(): string {\n\t\treturn $this->value;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function __toString(): string {\n\t\treturn $this->raw();\n\t}\n\n}\n"
  },
  {
    "path": "src/ValueObject/StringName.php",
    "content": "<?php\n\nnamespace IdeHelper\\ValueObject;\n\n/**\n * Holds a string with auto added `'` chars - can be auto-casted to string on output.\n */\nclass StringName implements ValueObjectInterface {\n\n\tprotected string $value;\n\n\t/**\n\t * @param string $value\n\t */\n\tprivate function __construct(string $value) {\n\t\t$this->value = $value;\n\t}\n\n\t/**\n\t * Creates itself from a string.\n\t *\n\t * @param string $value\n\t *\n\t * @return static\n\t */\n\tpublic static function create(string $value) {\n\t\treturn new static($value);\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function raw(): string {\n\t\treturn $this->value;\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tpublic function __toString(): string {\n\t\treturn '\\'' . $this->value . '\\'';\n\t}\n\n}\n"
  },
  {
    "path": "src/ValueObject/ValueObjectInterface.php",
    "content": "<?php\n\nnamespace IdeHelper\\ValueObject;\n\ninterface ValueObjectInterface {\n\n\t/**\n\t * Creates itself from a string.\n\t *\n\t * @param string $value\n\t *\n\t * @return static\n\t */\n\tpublic static function create(string $value);\n\n\t/**\n\t * Returns raw input.\n\t *\n\t * @return string\n\t */\n\tpublic function raw(): string;\n\n\t/**\n\t * Returns formatted output.\n\t *\n\t * @return string\n\t */\n\tpublic function __toString(): string;\n\n}\n"
  },
  {
    "path": "src/View/Helper/DocBlockHelper.php",
    "content": "<?php\n\nnamespace IdeHelper\\View\\Helper;\n\nuse Bake\\View\\Helper\\DocBlockHelper as BakeDocBlockHelper;\nuse Cake\\Core\\App;\nuse Cake\\Core\\Configure;\nuse Cake\\ORM\\Association;\nuse Cake\\Utility\\Inflector;\nuse IdeHelper\\Utility\\GenericString;\n\nclass DocBlockHelper extends BakeDocBlockHelper {\n\n\t/**\n\t * @var array<string, bool>|null\n\t */\n\tprotected static $nullableMap;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $virtualFields = [];\n\n\t/**\n\t * Overwrite Bake plugin class method until https://github.com/cakephp/bake/pull/470 lands.\n\t *\n\t * @param array<array<string, mixed>> $propertySchema The property schema to use for generating the type map.\n\t * @return array<string, string> The property DocType map.\n\t */\n\tpublic function buildEntityPropertyHintTypeMap(array $propertySchema): array {\n\t\t$properties = [];\n\t\tforeach ($propertySchema as $property => $info) {\n\t\t\tif ($info['kind'] === 'column') {\n\t\t\t\t$type = $this->columnTypeToHintType($info['type']);\n\n\t\t\t\t$properties[$property] = $this->columnTypeNullable($info, $type);\n\t\t\t}\n\t\t}\n\n\t\treturn $properties;\n\t}\n\n\t/**\n\t * @param array<string, string> $info\n\t * @param string|null $type\n\t * @param string|null $default\n\t *\n\t * @return string\n\t */\n\tpublic function columnTypeNullable(array $info, ?string $type, ?string $default = null): string {\n\t\tif (!$type) {\n\t\t\t$type = $default ?: 'mixed';\n\t\t}\n\n\t\tif ($type === 'mixed' || empty($info['null'])) {\n\t\t\treturn $type;\n\t\t}\n\n\t\tif (static::$nullableMap === null) {\n\t\t\tstatic::$nullableMap = (array)Configure::read('IdeHelper.nullableMap');\n\t\t}\n\n\t\tif (isset(static::$nullableMap[$type]) && static::$nullableMap[$type] === false) {\n\t\t\treturn $type;\n\t\t}\n\n\t\t$type .= '|null';\n\n\t\treturn $type;\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t *\n\t * Overwrite with nullable option for now until Bake is adjusted ( https://github.com/cakephp/bake/issues/579 )\n\t *\n\t * @param array<string, array<string, mixed>> $propertySchema The property schema to use for generating the type map.\n\t * @return array<string, string> The property DocType map.\n\t */\n\tpublic function buildEntityAssociationHintTypeMap(array $propertySchema): array {\n\t\t$properties = [];\n\t\tforeach ($propertySchema as $property => $info) {\n\t\t\tif ($info['kind'] === 'association') {\n\t\t\t\t$type = $this->associatedEntityTypeToHintType($info['type'], $info['association']);\n\t\t\t\tif ($info['association']->type() === Association::MANY_TO_ONE) {\n\t\t\t\t\t$key = $info['association']->getForeignKey();\n\t\t\t\t\tif (is_array($key)) {\n\t\t\t\t\t\t$key = implode('-', $key);\n\t\t\t\t\t}\n\t\t\t\t\t$properties = $this->_insertAfter(\n\t\t\t\t\t\t$properties,\n\t\t\t\t\t\t$key,\n\t\t\t\t\t\t[$property => $this->columnTypeNullable($info, $type)],\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\t$properties[$property] = $this->columnTypeNullable($info, $type);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn $properties;\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t *\n\t * Overwrite with array vs generics syntax switch.\n\t *\n\t * @param string $type The entity class type (a fully qualified class name).\n\t * @param \\Cake\\ORM\\Association $association The association related to the entity class.\n\t * @return string The DocBlock type\n\t */\n\tpublic function associatedEntityTypeToHintType(string $type, Association $association): string {\n\t\t$annotationType = $association->type();\n\t\tif (\n\t\t\t$annotationType === Association::MANY_TO_MANY ||\n\t\t\t$annotationType === Association::ONE_TO_MANY\n\t\t) {\n\t\t\treturn GenericString::generate($type);\n\t\t}\n\n\t\treturn $type;\n\t}\n\n\t/**\n\t * {@inheritDoc}\n\t *\n\t * Overwrite with array vs generics syntax switch.\n\t *\n\t * @param array<string, array<array<string, mixed>>> $associations Associations list.\n\t * @param array<string, array<string, mixed>> $associationInfo Association info.\n\t * @param array<string, array<mixed>> $behaviors Behaviors list.\n\t * @param string $entity Entity name.\n\t * @param string $namespace Namespace.\n\t * @return array<string>\n\t */\n\tpublic function buildTableAnnotations(\n\t\tarray $associations,\n\t\tarray $associationInfo,\n\t\tarray $behaviors,\n\t\tstring $entity,\n\t\tstring $namespace,\n\t): array {\n\t\t$annotations = [];\n\t\tforeach ($associations as $type => $assocs) {\n\t\t\tforeach ($assocs as $assoc) {\n\t\t\t\t$typeStr = Inflector::camelize($type);\n\t\t\t\tif (isset($associationInfo[$assoc['alias']])) {\n\t\t\t\t\t$tableFqn = $associationInfo[$assoc['alias']]['targetFqn'];\n\t\t\t\t\t$annotations[] = \"@property {$tableFqn}&\\Cake\\ORM\\Association\\\\{$typeStr} \\${$assoc['alias']}\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t$class = \"\\\\{$namespace}\\\\Model\\\\Entity\\\\{$entity}\";\n\t\t$classes = GenericString::generate($class);\n\t\t$classInterface = '\\\\Cake\\\\Datasource\\\\EntityInterface';\n\t\tif (Configure::read('IdeHelper.concreteEntitiesInParam')) {\n\t\t\t$classInterface = $class;\n\t\t}\n\n\t\t$generics = Configure::read('IdeHelper.genericsInParam');\n\t\t$detailed = $generics === 'detailed';\n\t\t$dataType = 'array';\n\t\t$dataListType = 'array';\n\t\t$optionsType = 'array';\n\t\t$itterable = 'iterable';\n\t\t$finderType = 'array|string';\n\t\t$findOrCreateSearchType = '\\Cake\\ORM\\Query\\SelectQuery|callable|array';\n\t\tif ($generics) {\n\t\t\t$dataType = $detailed ? 'array<string, mixed>' : 'array<mixed>';\n\t\t\t$dataListType = $detailed ? 'array<array<string, mixed>>' : 'array<mixed>';\n\t\t\t$optionsType = 'array<string, mixed>';\n\t\t\t$iterableEntity = $detailed ? $class : $classInterface;\n\t\t\t$itterable = \"iterable<{$iterableEntity}>\";\n\t\t}\n\t\tif ($detailed) {\n\t\t\t$finderType = 'array<string, mixed>|string';\n\t\t\t$findOrCreateSearchType = \"\\Cake\\ORM\\Query\\SelectQuery<{$class}>|callable|array<string, mixed>\";\n\t\t}\n\n\t\t$annotations[] = \"@method {$class} newEmptyEntity()\";\n\t\t$annotations[] = \"@method {$class} newEntity({$dataType} \\$data, {$optionsType} \\$options = [])\";\n\t\t$annotations[] = \"@method {$classes} newEntities({$dataListType} \\$data, {$optionsType} \\$options = [])\";\n\t\t$annotations[] = \"@method {$class} get(mixed \\$primaryKey, {$finderType} \\$finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null \\$cache = null, \\Closure|string|null \\$cacheKey = null, mixed ...\\$args)\";\n\t\t$annotations[] = \"@method {$class} findOrCreate({$findOrCreateSearchType} \\$search, ?callable \\$callback = null, {$optionsType} \\$options = [])\";\n\t\t$annotations[] = \"@method {$class} patchEntity({$classInterface} \\$entity, {$dataType} \\$data, {$optionsType} \\$options = [])\";\n\t\t$annotations[] = \"@method {$classes} patchEntities({$itterable} \\$entities, {$dataListType} \\$data, {$optionsType} \\$options = [])\";\n\t\t$annotations[] = \"@method {$class}|false save({$classInterface} \\$entity, {$optionsType} \\$options = [])\";\n\t\t$annotations[] = \"@method {$class} saveOrFail({$classInterface} \\$entity, {$optionsType} \\$options = [])\";\n\t\t$resultSet = $detailed ? \"\\Cake\\Datasource\\ResultSetInterface<int, {$class}>\" : \"{$classes}|\\Cake\\Datasource\\ResultSetInterface<{$class}>\";\n\t\t$annotations[] = \"@method {$resultSet}|false saveMany({$itterable} \\$entities, {$optionsType} \\$options = [])\";\n\t\t$annotations[] = \"@method {$resultSet} saveManyOrFail({$itterable} \\$entities, {$optionsType} \\$options = [])\";\n\t\t$annotations[] = \"@method {$resultSet}|false deleteMany({$itterable} \\$entities, {$optionsType} \\$options = [])\";\n\t\t$annotations[] = \"@method {$resultSet} deleteManyOrFail({$itterable} \\$entities, {$optionsType} \\$options = [])\";\n\n\t\tforeach ($behaviors as $behavior => $behaviorData) {\n\t\t\t$className = App::className($behavior, 'Model/Behavior', 'Behavior');\n\t\t\tif (!$className) {\n\t\t\t\t$className = \"Cake\\ORM\\Behavior\\\\{$behavior}Behavior\";\n\t\t\t}\n\n\t\t\t$annotations[] = '@mixin \\\\' . $className;\n\t\t}\n\n\t\treturn $annotations;\n\t}\n\n\t/**\n\t * @param array<string> $virtualFields\n\t *\n\t * @return void\n\t */\n\tpublic function setVirtualFields(array $virtualFields): void {\n\t\t$this->virtualFields = $virtualFields;\n\t}\n\n\t/**\n\t * @return array<string>\n\t */\n\tpublic function getVirtualFields(): array {\n\t\treturn $this->virtualFields;\n\t}\n\n}\n"
  },
  {
    "path": "tests/Fixture/BarBarsFixture.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass BarBarsFixture extends TestFixture {\n\n\t/**\n\t * Fields\n\t */\n\tpublic array $fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'content' => ['type' => 'text', 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null],\n\t\t'_constraints' => [\n\t\t\t'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],\n\t\t],\n\t\t'_options' => [\n\t\t\t'engine' => 'InnoDB',\n\t\t\t'collation' => 'utf8_general_ci',\n\t\t],\n\t];\n\n\t/**\n\t * Records\n\t */\n\tpublic array $records = [\n\t\t[\n\t\t\t'id' => 1,\n\t\t\t'name' => 'Lorem ipsum dolor sit amet',\n\t\t\t'content' => 'Lorem ipsum dolor sit amet',\n\t\t\t'created' => '2016-06-23 14:59:54',\n\t\t],\n\t];\n\n}\n"
  },
  {
    "path": "tests/Fixture/CarsFixture.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\nuse TestApp\\Model\\Enum\\CarStatus;\n\nclass CarsFixture extends TestFixture {\n\n\t/**\n\t * Fields\n\t */\n\tpublic array $fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'content' => ['type' => 'text', 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null],\n\t\t'modified' => ['type' => 'datetime', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null],\n\t\t'status' => ['type' => 'tinyinteger', 'length' => 2, 'null' => false, 'default' => '0'],\n\t\t'_constraints' => [\n\t\t\t'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],\n\t\t],\n\t\t'_options' => [\n\t\t\t'engine' => 'InnoDB',\n\t\t\t'collation' => 'utf8_general_ci',\n\t\t],\n\t];\n\n\t/**\n\t * Records\n\t */\n\tpublic array $records = [\n\t\t[\n\t\t\t'id' => 1,\n\t\t\t'name' => 'Lorem ipsum dolor sit amet',\n\t\t\t'content' => 'Lorem ipsum dolor sit amet',\n\t\t\t'created' => '2016-06-23 14:59:54',\n\t\t\t'modified' => '2016-06-23 14:59:54',\n\t\t\t'status' => CarStatus::NEW,\n\t\t],\n\t];\n\n}\n"
  },
  {
    "path": "tests/Fixture/FoosFixture.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass FoosFixture extends TestFixture {\n\n\t/**\n\t * Fields\n\t */\n\tpublic array $fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'content' => ['type' => 'text', 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null],\n\t\t'params' => ['type' => 'json', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null],\n\t\t'_constraints' => [\n\t\t\t'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],\n\t\t],\n\t\t'_options' => [\n\t\t\t'engine' => 'InnoDB',\n\t\t\t'collation' => 'utf8_general_ci',\n\t\t],\n\t];\n\n\t/**\n\t * Records\n\t */\n\tpublic array $records = [\n\t\t[\n\t\t\t'id' => 1,\n\t\t\t'name' => 'Lorem ipsum dolor sit amet',\n\t\t\t'content' => 'Lorem ipsum dolor sit amet',\n\t\t\t'created' => '2016-06-23 14:59:54',\n\t\t\t'params' => '[]',\n\t\t],\n\t];\n\n}\n"
  },
  {
    "path": "tests/Fixture/HousesFixture.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass HousesFixture extends TestFixture {\n\n\t/**\n\t * Fields\n\t */\n\tpublic array $fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'_constraints' => [\n\t\t\t'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],\n\t\t],\n\t\t'_options' => [\n\t\t\t'engine' => 'InnoDB',\n\t\t\t'collation' => 'utf8_general_ci',\n\t\t],\n\t];\n\n\t/**\n\t * Records\n\t */\n\tpublic array $records = [\n\t\t[\n\t\t\t'id' => 1,\n\t\t\t'name' => 'Lorem ipsum dolor sit amet',\n\t\t],\n\t];\n\n}\n"
  },
  {
    "path": "tests/Fixture/WheelsFixture.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass WheelsFixture extends TestFixture {\n\n\t/**\n\t * Fields\n\t */\n\tpublic array $fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'content' => ['type' => 'text', 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'created' => ['type' => 'datetime', 'length' => null, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null],\n\t\t'_constraints' => [\n\t\t\t'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],\n\t\t],\n\t\t'_options' => [\n\t\t\t'engine' => 'InnoDB',\n\t\t\t'collation' => 'utf8_general_ci',\n\t\t],\n\t];\n\n\t/**\n\t * Records\n\t */\n\tpublic array $records = [\n\t\t[\n\t\t\t'id' => 1,\n\t\t\t'name' => 'Lorem ipsum dolor sit amet',\n\t\t\t'content' => 'Lorem ipsum dolor sit amet',\n\t\t\t'created' => '2016-06-23 14:59:54',\n\t\t],\n\t];\n\n}\n"
  },
  {
    "path": "tests/Fixture/WindowsFixture.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass WindowsFixture extends TestFixture {\n\n\t/**\n\t * Fields\n\t */\n\tpublic array $fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'_constraints' => [\n\t\t\t'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],\n\t\t],\n\t\t'_options' => [\n\t\t\t'engine' => 'InnoDB',\n\t\t\t'collation' => 'utf8_general_ci',\n\t\t],\n\t];\n\n\t/**\n\t * Records\n\t */\n\tpublic array $records = [\n\t\t[\n\t\t\t'id' => 1,\n\t\t\t'name' => 'Lorem ipsum dolor sit amet',\n\t\t],\n\t];\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotation/AnnotationFactoryTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotation;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotation\\AnnotationFactory;\nuse IdeHelper\\Annotation\\ExtendsAnnotation;\nuse IdeHelper\\Annotation\\MethodAnnotation;\nuse IdeHelper\\Annotation\\MixinAnnotation;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse IdeHelper\\Annotation\\PropertyReadAnnotation;\nuse IdeHelper\\Annotation\\UsesAnnotation;\n\nclass AnnotationFactoryTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCreate() {\n\t\t$annotation = AnnotationFactory::create('@method', '\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'doSth()', 1);\n\t\t$this->assertInstanceOf(MethodAnnotation::class, $annotation);\n\n\t\t$annotation = AnnotationFactory::create('@property', '\\\\Foo\\\\Model\\\\Entity\\\\Bar', '$baz', 1);\n\t\t$this->assertInstanceOf(PropertyAnnotation::class, $annotation);\n\n\t\t$annotation = AnnotationFactory::create('@property', '\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'baz', 1);\n\t\t$this->assertInstanceOf(PropertyAnnotation::class, $annotation);\n\n\t\t$annotation = AnnotationFactory::create('@property-read', '\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'baz', 1);\n\t\t$this->assertInstanceOf(PropertyReadAnnotation::class, $annotation);\n\n\t\t$annotation = AnnotationFactory::create('@mixin', '\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$this->assertInstanceOf(MixinAnnotation::class, $annotation);\n\n\t\t$annotation = AnnotationFactory::create('@uses', '\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$this->assertInstanceOf(UsesAnnotation::class, $annotation);\n\n\t\t$annotation = AnnotationFactory::create('@foooo', '\\\\Foo', '$foo');\n\t\t$this->assertNull($annotation);\n\n\t\t$annotation = AnnotationFactory::create('@mixin', '\\\\Foo', '!');\n\t\t$this->assertInstanceOf(MixinAnnotation::class, $annotation);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\n\t\t$annotation = AnnotationFactory::create('@extends', '\\\\Foo<array{Bar: \\Bar}>', '!');\n\t\t$this->assertInstanceOf(ExtendsAnnotation::class, $annotation);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCreateFromString() {\n\t\t/** @var \\IdeHelper\\Annotation\\MethodAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@method \\\\Foo\\\\Model\\\\Entity\\\\Bar doSth($x, $y, $z)');\n\t\t$this->assertInstanceOf(MethodAnnotation::class, $annotation);\n\t\t$this->assertSame('doSth($x, $y, $z)', $annotation->getMethod());\n\n\t\t/** @var \\IdeHelper\\Annotation\\PropertyAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@property \\\\Foo\\\\Model\\\\Entity\\\\Bar $baz');\n\t\t$this->assertInstanceOf(PropertyAnnotation::class, $annotation);\n\t\t$this->assertSame('$baz', $annotation->getProperty());\n\n\t\t/** @var \\IdeHelper\\Annotation\\PropertyAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@property \\\\Foo\\\\Model\\\\Entity\\\\Bar baz');\n\t\t$this->assertInstanceOf(PropertyAnnotation::class, $annotation);\n\t\t$this->assertSame('$baz', $annotation->getProperty());\n\n\t\t/** @var \\IdeHelper\\Annotation\\PropertyAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@property \\\\Foo\\\\Model\\\\Entity\\\\Bar $baz Some comment :)');\n\t\t$this->assertInstanceOf(PropertyAnnotation::class, $annotation);\n\t\t$this->assertSame('Some comment :)', $annotation->getDescription());\n\n\t\t$annotation = AnnotationFactory::createFromString('@property\\\\Foo\\\\Model\\\\Entity\\\\Bar$baz');\n\t\t$this->assertNull($annotation);\n\n\t\t/** @var \\IdeHelper\\Annotation\\PropertyReadAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@property-read \\\\Foo\\\\Model\\\\Entity\\\\Bar baz');\n\t\t$this->assertInstanceOf(PropertyReadAnnotation::class, $annotation);\n\t\t$this->assertSame('$baz', $annotation->getProperty());\n\n\t\t/** @var \\IdeHelper\\Annotation\\MethodAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@method \\\\Foo\\\\Model\\\\Entity\\\\Bar complex($x, $y = [], $z = null)');\n\t\t$this->assertInstanceOf(MethodAnnotation::class, $annotation);\n\t\t$this->assertSame('complex($x, $y = [], $z = null)', $annotation->getMethod());\n\n\t\t/** @var \\IdeHelper\\Annotation\\MethodAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@method \\\\Foo\\\\Model\\\\Entity\\\\Bar complex($x, $y = [], $z = null) !');\n\t\t$this->assertInstanceOf(MethodAnnotation::class, $annotation);\n\t\t$this->assertSame('complex($x, $y = [], $z = null)', $annotation->getMethod());\n\n\t\t/** @var \\IdeHelper\\Annotation\\MixinAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@mixin \\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$this->assertInstanceOf(MixinAnnotation::class, $annotation);\n\t\t$this->assertSame('', $annotation->getDescription());\n\n\t\t/** @var \\IdeHelper\\Annotation\\MixinAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@mixin \\\\Foo\\\\Model\\\\Entity\\\\Bar !');\n\t\t$this->assertInstanceOf(MixinAnnotation::class, $annotation);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\n\t\t/** @var \\IdeHelper\\Annotation\\UsesAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@uses \\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$this->assertInstanceOf(UsesAnnotation::class, $annotation);\n\t\t$this->assertSame('', $annotation->getDescription());\n\n\t\t/** @var \\IdeHelper\\Annotation\\UsesAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@extends \\App\\Model\\Table\\Table<array{Typographic: \\Tools\\Model\\Behavior\\TypographicBehavior>');\n\t\t$this->assertInstanceOf(ExtendsAnnotation::class, $annotation);\n\t\t$this->assertSame('', $annotation->getDescription());\n\n\t\t/** @var \\IdeHelper\\Annotation\\UsesAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@extends \\App\\Model\\Table\\Table<array{Typographic: \\Tools\\Model\\Behavior\\TypographicBehavior, Rating: \\Ratings\\Model\\Behavior\\RatableBehavior> !');\n\t\t$this->assertInstanceOf(ExtendsAnnotation::class, $annotation);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\t}\n\n\t/**\n\t * Regression: types with spaces inside generic brackets (e.g. `ResultSetInterface<int, \\Entity>`)\n\t * used to be split at the first space, putting part of the type into the method name.\n\t * That caused `matches()` to fail and annotations to be duplicated on subsequent runs\n\t * when `genericsInParam: 'detailed'` was enabled.\n\t *\n\t * @return void\n\t */\n\tpublic function testCreateFromStringWithSpaceInsideGenericType() {\n\t\t/** @var \\IdeHelper\\Annotation\\MethodAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@method \\Cake\\Datasource\\ResultSetInterface<int, \\Foo\\Model\\Entity\\Bar>|false saveMany(iterable<\\Foo\\Model\\Entity\\Bar> $entities, array<string, mixed> $options = [])');\n\t\t$this->assertInstanceOf(MethodAnnotation::class, $annotation);\n\t\t$this->assertSame('\\Cake\\Datasource\\ResultSetInterface<int, \\Foo\\Model\\Entity\\Bar>|false', $annotation->getType());\n\t\t$this->assertSame('saveMany(iterable<\\Foo\\Model\\Entity\\Bar> $entities, array<string, mixed> $options = [])', $annotation->getMethod());\n\n\t\t/** @var \\IdeHelper\\Annotation\\PropertyAnnotation $annotation */\n\t\t$annotation = AnnotationFactory::createFromString('@property \\Cake\\ORM\\Association\\BelongsTo<\\Foo\\Model\\Table\\BarsTable> $Bar');\n\t\t$this->assertInstanceOf(PropertyAnnotation::class, $annotation);\n\t\t$this->assertSame('\\Cake\\ORM\\Association\\BelongsTo<\\Foo\\Model\\Table\\BarsTable>', $annotation->getType());\n\t\t$this->assertSame('$Bar', $annotation->getProperty());\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotation/ExtendsAnnotationTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotation;\n\nuse IdeHelper\\Annotation\\ExtendsAnnotation;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse RuntimeException;\nuse Shim\\TestSuite\\TestCase;\n\nclass ExtendsAnnotationTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$annotation = new ExtendsAnnotation('\\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior>');\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@extends \\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior>', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWith() {\n\t\t$replacementAnnotation = new ExtendsAnnotation('\\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior>');\n\n\t\t$annotation = new ExtendsAnnotation('\\Table<array{X: \\Y>');\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@extends \\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior>', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatches() {\n\t\t$annotation = new ExtendsAnnotation('\\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior>');\n\t\t$comparisonAnnotation = new ExtendsAnnotation('\\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior>');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\n\t\t$annotation = new ExtendsAnnotation('\\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior>');\n\t\t$comparisonAnnotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', '$bar');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\n\t\t$annotation = new ExtendsAnnotation('\\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior>');\n\t\t$comparisonAnnotation = new ExtendsAnnotation('\\Table<array{Xyz: \\Plugin\\Model\\Behavior\\XyzBehavior>');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatchesWithDescription() {\n\t\t$annotation = new ExtendsAnnotation('\\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior> !');\n\t\t$comparisonAnnotation = new ExtendsAnnotation('\\Table<array{Sluggable: \\Plugin\\Model\\Behavior\\SluggableBehavior>');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\n\t\t$this->assertTrue($result);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\t\t$this->assertSame('', $comparisonAnnotation->getDescription());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIndex() {\n\t\t$annotation = new ExtendsAnnotation('', 1);\n\n\t\t$this->assertTrue($annotation->hasIndex());\n\t\t$this->assertSame(1, $annotation->getIndex());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIndexInvalidCall() {\n\t\t$annotation = new ExtendsAnnotation('');\n\n\t\t$this->assertFalse($annotation->hasIndex());\n\n\t\t$this->expectException(RuntimeException::class);\n\n\t\t$annotation->getIndex();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotation/MethodAnnotationTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotation;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotation\\MethodAnnotation;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse RuntimeException;\n\nclass MethodAnnotationTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$annotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'doSth()');\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@method \\\\Foo\\\\Model\\\\Entity\\\\Bar doSth()', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWith() {\n\t\t$replacementAnnotation = new MethodAnnotation('\\\\Something\\\\Model\\\\Entity\\\\Else', 'doSth(array $options = [])');\n\n\t\t$annotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'doSth()');\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@method \\\\Something\\\\Model\\\\Entity\\\\Else doSth(array $options = [])', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatches() {\n\t\t$annotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'doSth()');\n\t\t$comparisonAnnotation = new MethodAnnotation('\\\\Something\\\\Else', 'doSth()');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\n\t\t$annotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'doSth()');\n\t\t$comparisonAnnotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'sthElse()');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\n\t\t$annotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'doSth()');\n\t\t$comparisonAnnotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', 'doSth()');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatchesWithDescription() {\n\t\t$annotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', 'doSth() !');\n\t\t$comparisonAnnotation = new MethodAnnotation('\\\\Something\\\\Else', 'doSth()');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\n\t\t$this->assertTrue($result);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\t\t$this->assertSame('', $comparisonAnnotation->getDescription());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIndex() {\n\t\t$annotation = new MethodAnnotation('', '', 1);\n\n\t\t$this->assertTrue($annotation->hasIndex());\n\t\t$this->assertSame(1, $annotation->getIndex());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIndexInvalidCall() {\n\t\t$annotation = new MethodAnnotation('', '');\n\n\t\t$this->assertFalse($annotation->hasIndex());\n\n\t\t$this->expectException(RuntimeException::class);\n\n\t\t$annotation->getIndex();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotation/MixinAnnotationTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotation;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotation\\MixinAnnotation;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse RuntimeException;\n\nclass MixinAnnotationTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$annotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@mixin \\\\Foo\\\\Model\\\\Entity\\\\Bar', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWith() {\n\t\t$replacementAnnotation = new MixinAnnotation('\\\\Something\\\\Model\\\\Entity\\\\Else');\n\n\t\t$annotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@mixin \\\\Something\\\\Model\\\\Entity\\\\Else', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatches() {\n\t\t$annotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$comparisonAnnotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\n\t\t$annotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$comparisonAnnotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\BarBaz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\n\t\t$annotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$comparisonAnnotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', '$bar');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatchesWithDescription() {\n\t\t$annotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar !');\n\t\t$comparisonAnnotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\t\t$this->assertSame('', $comparisonAnnotation->getDescription());\n\n\t\t$annotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$comparisonAnnotation = new MixinAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar !');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\t\t$this->assertSame('', $annotation->getDescription());\n\t\t$this->assertSame('!', $comparisonAnnotation->getDescription());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIndex() {\n\t\t$annotation = new MixinAnnotation('', 1);\n\n\t\t$this->assertTrue($annotation->hasIndex());\n\t\t$this->assertSame(1, $annotation->getIndex());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIndexInvalidCall() {\n\t\t$annotation = new MixinAnnotation('');\n\n\t\t$this->assertFalse($annotation->hasIndex());\n\n\t\t$this->expectException(RuntimeException::class);\n\n\t\t$annotation->getIndex();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotation/ParamAnnotationTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotation;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotation\\MethodAnnotation;\nuse IdeHelper\\Annotation\\ParamAnnotation;\n\nclass ParamAnnotationTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$annotation = new ParamAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@param \\\\Foo\\\\Model\\\\Table\\\\Bar $baz', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWith() {\n\t\t$replacementAnnotation = new ParamAnnotation('\\\\Something\\\\Model\\\\Table\\\\Else', '$baz');\n\n\t\t$annotation = new ParamAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@param \\\\Something\\\\Model\\\\Table\\\\Else $baz', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatches() {\n\t\t$annotation = new ParamAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new ParamAnnotation('\\\\Something\\\\Else', '$baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\n\t\t$annotation = new ParamAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new ParamAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$bbb');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\n\t\t$annotation = new ParamAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatchesWithDescription() {\n\t\t$annotation = new ParamAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz !');\n\t\t$comparisonAnnotation = new ParamAnnotation('\\\\Something\\\\Else', '$baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\n\t\t$this->assertTrue($result);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\t\t$this->assertSame('', $comparisonAnnotation->getDescription());\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotation/PropertyAnnotationTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotation;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotation\\MethodAnnotation;\nuse IdeHelper\\Annotation\\PropertyAnnotation;\n\nclass PropertyAnnotationTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$annotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@property \\\\Foo\\\\Model\\\\Table\\\\Bar $baz', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWith() {\n\t\t$replacementAnnotation = new PropertyAnnotation('\\\\Something\\\\Model\\\\Table\\\\Else', '$baz');\n\n\t\t$annotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@property \\\\Something\\\\Model\\\\Table\\\\Else $baz', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatches() {\n\t\t$annotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new PropertyAnnotation('\\\\Something\\\\Else', '$baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\n\t\t$annotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$bbb');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\n\t\t$annotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatchesWithDescription() {\n\t\t$annotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz !');\n\t\t$comparisonAnnotation = new PropertyAnnotation('\\\\Something\\\\Else', '$baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\n\t\t$this->assertTrue($result);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\t\t$this->assertSame('', $comparisonAnnotation->getDescription());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatchesWithoutVariableChar() {\n\t\t$annotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new PropertyAnnotation('\\\\Something\\\\Else', 'baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotation/UsesAnnotationTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotation;\n\nuse IdeHelper\\Annotation\\PropertyAnnotation;\nuse IdeHelper\\Annotation\\UsesAnnotation;\nuse RuntimeException;\nuse Shim\\TestSuite\\TestCase;\n\nclass UsesAnnotationTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$annotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@uses \\\\Foo\\\\Model\\\\Entity\\\\Bar', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWith() {\n\t\t$replacementAnnotation = new UsesAnnotation('\\\\Something\\\\Model\\\\Entity\\\\Else');\n\n\t\t$annotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@uses \\\\Something\\\\Model\\\\Entity\\\\Else', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatches() {\n\t\t$annotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$comparisonAnnotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\n\t\t$annotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$comparisonAnnotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\BarBaz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\n\t\t$annotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$comparisonAnnotation = new PropertyAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar', '$bar');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatchesWithDescription() {\n\t\t$annotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar !');\n\t\t$comparisonAnnotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\t\t$this->assertSame('', $comparisonAnnotation->getDescription());\n\n\t\t$annotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar');\n\t\t$comparisonAnnotation = new UsesAnnotation('\\\\Foo\\\\Model\\\\Entity\\\\Bar !');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\t\t$this->assertSame('', $annotation->getDescription());\n\t\t$this->assertSame('!', $comparisonAnnotation->getDescription());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIndex() {\n\t\t$annotation = new UsesAnnotation('', 1);\n\n\t\t$this->assertTrue($annotation->hasIndex());\n\t\t$this->assertSame(1, $annotation->getIndex());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIndexInvalidCall() {\n\t\t$annotation = new UsesAnnotation('');\n\n\t\t$this->assertFalse($annotation->hasIndex());\n\n\t\t$this->expectException(RuntimeException::class);\n\n\t\t$annotation->getIndex();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotation/VariableAnnotationTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotation;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotation\\MethodAnnotation;\nuse IdeHelper\\Annotation\\VariableAnnotation;\n\nclass VariableAnnotationTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$annotation = new VariableAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@var \\\\Foo\\\\Model\\\\Table\\\\Bar $baz', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWith() {\n\t\t$replacementAnnotation = new VariableAnnotation('\\\\Something\\\\Model\\\\Table\\\\Else', '$baz');\n\n\t\t$annotation = new VariableAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@var \\\\Something\\\\Model\\\\Table\\\\Else $baz', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatches() {\n\t\t$annotation = new VariableAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new VariableAnnotation('\\\\Something\\\\Else', '$baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertTrue($result);\n\n\t\t$annotation = new VariableAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new VariableAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$bbb');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\n\t\t$annotation = new VariableAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$comparisonAnnotation = new MethodAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testMatchesWithDescription() {\n\t\t$annotation = new VariableAnnotation('\\\\Foo\\\\Model\\\\Table\\\\Bar', '$baz !');\n\t\t$comparisonAnnotation = new VariableAnnotation('\\\\Something\\\\Else', '$baz');\n\t\t$result = $annotation->matches($comparisonAnnotation);\n\n\t\t$this->assertTrue($result);\n\t\t$this->assertSame('!', $annotation->getDescription());\n\t\t$this->assertSame('', $comparisonAnnotation->getDescription());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWithPreservesNullableForGuessed() {\n\t\t// Existing annotation has |null\n\t\t$annotation = new VariableAnnotation('\\\\App\\\\Model\\\\Entity\\\\Home|null', '$homeData');\n\n\t\t// New guessed annotation without null\n\t\t$replacementAnnotation = new VariableAnnotation('mixed', '$homeData');\n\t\t$replacementAnnotation->setGuessed(true);\n\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t// Should preserve |null\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@var mixed|null $homeData', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWithPreservesNullForNonGuessed() {\n\t\t// Existing annotation has |null\n\t\t$annotation = new VariableAnnotation('\\\\App\\\\Model\\\\Entity\\\\Home|null', '$homeData');\n\n\t\t// New non-guessed annotation without null\n\t\t$replacementAnnotation = new VariableAnnotation('\\\\App\\\\Model\\\\Entity\\\\User', '$homeData');\n\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t// Should preserve |null since user explicitly added it\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@var \\\\App\\\\Model\\\\Entity\\\\User|null $homeData', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testReplaceWithDoesNotDuplicateNull() {\n\t\t// Existing annotation has |null\n\t\t$annotation = new VariableAnnotation('\\\\App\\\\Model\\\\Entity\\\\Home|null', '$homeData');\n\n\t\t// New guessed annotation already has null\n\t\t$replacementAnnotation = new VariableAnnotation('object|null', '$homeData');\n\t\t$replacementAnnotation->setGuessed(true);\n\n\t\t$annotation->replaceWith($replacementAnnotation);\n\n\t\t// Should NOT duplicate |null\n\t\t$result = (string)$annotation;\n\t\t$this->assertSame('@var object|null $homeData', $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/CallbackAnnotatorTask/VirtualFieldCallbackAnnotatorTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator\\CallbackAnnotatorTask;\n\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\CallbackAnnotatorTask\\VirtualFieldCallbackAnnotatorTask;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse Shim\\TestSuite\\TestCase;\n\nclass VirtualFieldCallbackAnnotatorTaskTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected ?Io $io = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRun() {\n\t\t$task = $this->getTask('', '');\n\n\t\t$result = $task->shouldRun('/src/Model/Entity/Foo1.php');\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('/src/Model/Table/Foo2.php');\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$content = file_get_contents(TEST_FILES . 'VirtualFieldAnnotation' . DS . 'VirtualFieldAnnotation.missing.php');\n\t\t$path = '/src/Model/Entity/Foo.php';\n\t\t$task = $this->getTask($path, $content);\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertTrue($result);\n\n\t\t$content = $task->getContent();\n\t\t$this->assertTextContains('* @see \\TestApp\\Model\\Entity\\Foo::$expected_release_type', $content);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateExisting() {\n\t\t$content = file_get_contents(TEST_FILES . 'VirtualFieldAnnotation' . DS . 'VirtualFieldAnnotation.existing.php');\n\t\t$path = '/src/Model/Entity/Foo.php';\n\t\t$task = $this->getTask($path, $content);\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertTrue($result);\n\n\t\t$content = $task->getContent();\n\t\t$count = substr_count($content, '@see');\n\t\t// We cannot avoid the duplication for incomplete tags for now\n\t\t$this->assertSame(3, $count, 'Count is ' . $count);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('@see \\TestApp\\Model\\Entity\\Foo::$something', $output);\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t * @param array $params\n\t *\n\t * @return \\IdeHelper\\Annotator\\CallbackAnnotatorTask\\VirtualFieldCallbackAnnotatorTask\n\t */\n\tprotected function getTask(string $path, string $content, array $params = []) {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new VirtualFieldCallbackAnnotatorTask($this->io, $params, $path, $content);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/CallbackAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\CallbackAnnotator;\nuse IdeHelper\\Console\\Io;\nuse RuntimeException;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass CallbackAnnotatorTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\n\t\t$file = TMP . 'CallbacksTable.php';\n\t\tif (file_exists($file)) {\n\t\t\tunlink($file);\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\t$file = TMP . 'CallbacksTable.php';\n\t\tif (file_exists($file)) {\n\t\t\tunlink($file);\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$path = APP . 'Model/Table/CallbacksTable.php';\n\t\t$execPath = TMP . 'CallbacksTable.php';\n\t\tcopy($path, $execPath);\n\n\t\t$annotator->annotate($execPath);\n\n\t\t$content = file_get_contents($execPath);\n\t\tif ($content === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\n\t\t$testPath = TEST_FILES . 'Model/Table/CallbacksTable.php';\n\t\t$expectedContent = file_get_contents($testPath);\n\t\tif ($expectedContent === false) {\n\t\t\tthrow new RuntimeException('Cannot read file');\n\t\t}\n\t\t$this->assertTextEquals($expectedContent, $content);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('  -> 2 annotations updated.', $output);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\CallbackAnnotator\n\t */\n\tprotected function _getAnnotatorMock(array $params) {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new CallbackAnnotator($this->io, $params);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ClassAnnotatorTask/FormClassAnnotatorTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\FormClassAnnotatorTask;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse Shim\\TestSuite\\TestCase;\n\nclass FormClassAnnotatorTaskTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected ?Io $io = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRun() {\n\t\t$task = $this->getTask('');\n\n\t\t$content = 'namespace TestApp\\\\Foo' . PHP_EOL . 'use TestApp\\\\Form\\\\DocForm' . PHP_EOL . '$docForm->execute()';\n\t\t$result = $task->shouldRun('/src/Foo.php', $content);\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('/tests/Foo.php', $content);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * Test that nested namespace Forms (e.g. App\\Form\\Admin\\ContactForm) don't cause regex errors.\n\t *\n\t * Without preg_quote(), the backslash in \"Admin\\Contact\" would cause:\n\t * \"preg_match(): Compilation failed: unrecognized character follows \\\"\n\t *\n\t * @return void\n\t */\n\tpublic function testShouldRunNestedNamespace() {\n\t\t$task = $this->getTask('');\n\n\t\t// Nested namespace Form class with matching variable name (contains backslash)\n\t\t// lcfirst('Admin\\Contact') = 'admin\\Contact', + 'Form' = 'admin\\ContactForm'\n\t\t$content = 'namespace TestApp\\\\Foo' . PHP_EOL . 'use TestApp\\\\Form\\\\Admin\\\\ContactForm' . PHP_EOL . '$admin\\\\ContactForm->execute()';\n\t\t$result = $task->shouldRun('/src/Foo.php', $content);\n\t\t$this->assertTrue($result);\n\n\t\t// Without the execute() call, should return false\n\t\t$content = 'namespace TestApp\\\\Foo' . PHP_EOL . 'use TestApp\\\\Form\\\\Admin\\\\ContactForm' . PHP_EOL . '$admin\\\\ContactForm->something()';\n\t\t$result = $task->shouldRun('/src/Foo.php', $content);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$content = file_get_contents(TEST_FILES . 'FormAnnotation' . DS . 'FormAnnotation.missing.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Foo/Foo.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertTrue($result);\n\n\t\t$content = $task->getContent();\n\t\t$this->assertTextContains('* @uses \\TestApp\\Form\\DocForm::_execute()', $content);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateExisting() {\n\t\t$content = file_get_contents(TEST_FILES . 'FormAnnotation' . DS . 'FormAnnotation.existing.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Foo/Foo.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertFalse($result);\n\n\t\t$content = $task->getContent();\n\t\t$count = substr_count($content, '@uses');\n\t\t$this->assertSame(1, $count);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertSame('', $output);\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param array $params\n\t *\n\t * @return \\IdeHelper\\Annotator\\ClassAnnotatorTask\\FormClassAnnotatorTask\n\t */\n\tprotected function getTask(string $content, array $params = []) {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new FormClassAnnotatorTask($this->io, $params, $content);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ClassAnnotatorTask/MailerClassAnnotatorTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\MailerClassAnnotatorTask;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse Shim\\TestSuite\\TestCase;\n\nclass MailerClassAnnotatorTaskTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected ?Io $io = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRun() {\n\t\t$task = $this->getTask('');\n\n\t\t$content = 'namespace TestApp\\\\Foo' . PHP_EOL . 'use TestApp\\\\Mailer\\\\NotificationMailer' . PHP_EOL . '$notificationMailer->send(\\'notify\\')';\n\t\t$result = $task->shouldRun('/src/Foo.php', $content);\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('/tests/Foo.php', $content);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRunViaCall() {\n\t\t$task = $this->getTask('');\n\n\t\t$content = 'namespace TestApp\\\\Foo' . PHP_EOL . '$notificationMailer = $this->getMailer(\\'Notification\\')' . PHP_EOL . '$notificationMailer->send(\\'notify\\')';\n\t\t$result = $task->shouldRun('/src/Foo.php', $content);\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('/tests/Foo.php', $content);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRunViaCallPlugin() {\n\t\t$task = $this->getTask('');\n\n\t\t$content = 'namespace TestApp\\\\Foo' . PHP_EOL . '$notificationMailer = $this->getMailer(\\'FooBar.Notification\\')' . PHP_EOL . '$notificationMailer->send(\\'notify\\')';\n\t\t$result = $task->shouldRun('/src/Foo.php', $content);\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('/tests/Foo.php', $content);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$content = file_get_contents(TEST_FILES . 'MailerAnnotation' . DS . 'MailerAnnotation.missing.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Foo/Foo.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertTrue($result);\n\n\t\t$content = $task->getContent();\n\t\t$this->assertTextContains('* @uses \\TestApp\\Mailer\\NotificationMailer::notify()', $content);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSingleLine() {\n\t\t$content = file_get_contents(TEST_FILES . 'MailerAnnotation' . DS . 'MailerAnnotation.missing2.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Foo/Foo.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertTrue($result);\n\n\t\t$content = $task->getContent();\n\t\t$this->assertTextContains('* @uses \\TestApp\\Mailer\\NotificationMailer::notify()', $content);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateMultiLine() {\n\t\t$content = file_get_contents(TEST_FILES . 'MailerAnnotation' . DS . 'MailerAnnotation.missing3.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Foo/Foo.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertTrue($result);\n\n\t\t$content = $task->getContent();\n\t\t$this->assertTextContains('* @uses \\TestApp\\Mailer\\NotificationMailer::notify()', $content);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateExisting() {\n\t\t$content = file_get_contents(TEST_FILES . 'MailerAnnotation' . DS . 'MailerAnnotation.existing.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Foo/Foo.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertFalse($result);\n\n\t\t$content = $task->getContent();\n\t\t$count = substr_count($content, '@uses');\n\t\t$this->assertSame(1, $count);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertSame('', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRunInvalid() {\n\t\t$content = file_get_contents(TEST_FILES . 'MailerAnnotation' . DS . 'MailerAnnotation.invalid.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Foo/Foo.php';\n\n\t\t$result = $task->shouldRun($path, $content);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param array $params\n\t *\n\t * @return \\IdeHelper\\Annotator\\ClassAnnotatorTask\\MailerClassAnnotatorTask\n\t */\n\tprotected function getTask(string $content, array $params = []) {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new MailerClassAnnotatorTask($this->io, $params, $content);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ClassAnnotatorTask/TableFindAnnotatorTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\TableFindAnnotatorTask;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse Shim\\TestSuite\\TestCase;\n\nclass TableFindAnnotatorTaskTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRun(): void {\n\t\t$task = $this->getTask('');\n\n\t\t$result = $task->shouldRun('/src/Foo.php', '');\n\t\t$this->assertFalse($result);\n\n\t\t$result = $task->shouldRun('/src/Foo.php', '$x = $this->Table->find()->first();');\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('/src/Foo.php', '$x = $this->Table->find()->firstOrFail();');\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('/tests/Foo.php', '$x = $this->Table->find()->first();');\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate(): void {\n\t\t$content = file_get_contents(TEST_FILES . 'ClassAnnotation/TableFind/before.php');\n\t\t$this->assertIsString($content);\n\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Controller/TestMeController.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertTrue($result);\n\n\t\t$content = $task->getContent();\n\t\t// Only $this->Residents patterns should be detected\n\t\t$this->assertStringContainsString('/** @var \\TestApp\\Model\\Entity\\Resident|null $residentX */', $content);\n\t\t$this->assertStringContainsString('/** @var \\TestApp\\Model\\Entity\\Resident $residentY */', $content);\n\n\t\t// The $residentsTable patterns should NOT have annotations (not $this->TableName)\n\t\t$this->assertStringNotContainsString('@var \\TestApp\\Model\\Entity\\Resident|null $resident */', $content);\n\t\t$this->assertStringNotContainsString('@var \\TestApp\\Model\\Entity\\Resident $residentOther */', $content);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateNoMatches(): void {\n\t\t$content = <<<'PHP'\n<?php\nclass FooController {\n\tpublic function test(): void {\n\t\t$table = $this->fetchTable('Users');\n\t\t$user = $table->find()->first();\n\t}\n}\nPHP;\n\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Controller/FooController.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateAlreadyAnnotated(): void {\n\t\t$content = <<<'PHP'\n<?php\nclass FooController {\n\tpublic function test(): void {\n\t\t/** @var \\App\\Model\\Entity\\User|null $user */\n\t\t$user = $this->Users->find()->first();\n\t}\n}\nPHP;\n\n\t\t$task = $this->getTask($content);\n\t\t$path = '/src/Controller/FooController.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param array<string, mixed> $params\n\t * @return \\IdeHelper\\Annotator\\ClassAnnotatorTask\\TableFindAnnotatorTask\n\t */\n\tprotected function getTask(string $content, array $params = []): TableFindAnnotatorTask {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new TableFindAnnotatorTask($this->io, $params, $content);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ClassAnnotatorTask/TestClassAnnotatorTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator\\ClassAnnotatorTask;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\TestClassAnnotatorTask;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse Shim\\TestSuite\\TestCase;\n\nclass TestClassAnnotatorTaskTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected ?Io $io = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRun() {\n\t\t$task = $this->getTask('');\n\n\t\t$content = 'namespace TestApp\\Test\\TestCase\\Controller' . PHP_EOL . 'class FooControllerTest extends ControllerIntegrationTestCase';\n\t\t$result = $task->shouldRun('/tests/TestCase/Foo.php', $content);\n\t\t$this->assertTrue($result);\n\n\t\t$content = 'namespace TestApp\\Test\\TestCase\\Command' . PHP_EOL . 'class FooCommandTest extends ConsoleIntegrationTestCase';\n\t\t$result = $task->shouldRun('/tests/TestCase/Foo.php', $content);\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('/tests/TestCase/Foo.php', 'namespace TestApp\\Foo');\n\t\t$this->assertFalse($result);\n\n\t\t$result = $task->shouldRun('/tests/Foo.php', $content);\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$content = file_get_contents(TEST_FILES . 'tests' . DS . 'BarControllerTest.missing.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/tests/TestCase/Controller/BarControllerTest.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertTrue($result);\n\n\t\t$content = $task->getContent();\n\t\t$this->assertTextContains('* @link \\TestApp\\Controller\\BarController', $content);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateExisting() {\n\t\t$content = file_get_contents(TEST_FILES . 'tests' . DS . 'BarControllerTest.existing.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/tests/TestCase/Controller/BarControllerTest.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertFalse($result);\n\n\t\t$content = $task->getContent();\n\t\t$count = substr_count($content, '@uses');\n\t\t$this->assertSame(1, $count);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertSame('', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotatePreferUses() {\n\t\tConfigure::write('IdeHelper.preferLinkOverUsesInTests', false);\n\n\t\t$content = file_get_contents(TEST_FILES . 'tests' . DS . 'BarControllerTest.missing.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/tests/TestCase/Controller/BarControllerTest.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertTrue($result);\n\n\t\t$content = $task->getContent();\n\t\t$this->assertTextContains('* @uses \\TestApp\\Controller\\BarController', $content);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSkipsWhenUsesClassAttribute() {\n\t\t$content = file_get_contents(TEST_FILES . 'tests' . DS . 'BarControllerTest.attribute.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/tests/TestCase/Controller/BarControllerTest.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertFalse($result);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertSame('', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateExistingLink() {\n\t\t$content = file_get_contents(TEST_FILES . 'tests' . DS . 'BarControllerTest.link.php');\n\t\t$task = $this->getTask($content);\n\t\t$path = '/tests/TestCase/Controller/BarControllerTest.php';\n\n\t\t$result = $task->annotate($path);\n\t\t$this->assertFalse($result);\n\n\t\t$content = $task->getContent();\n\t\t$count = substr_count($content, '@link');\n\t\t$this->assertSame(1, $count);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertSame('', $output);\n\t}\n\n\t/**\n\t * @param string $content\n\t * @param array $params\n\t *\n\t * @return \\IdeHelper\\Annotator\\ClassAnnotatorTask\\TestClassAnnotatorTask\n\t */\n\tprotected function getTask(string $content, array $params = []) {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new TestClassAnnotatorTask($this->io, $params, $content);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ClassAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ClassAnnotator;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass ClassAnnotatorTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\n\t\t$file = TMP . 'src' . DS . 'CustomClass.php';\n\t\tif (file_exists($file)) {\n\t\t\tunlink($file);\n\t\t\trmdir(TMP . 'src' . DS);\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\t$file = TMP . 'src' . DS . 'CustomClass.php';\n\t\tif (file_exists($file)) {\n\t\t\tunlink($file);\n\t\t\trmdir(TMP . 'src' . DS);\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$path = APP . 'Custom' . DS . 'CustomClass.php';\n\t\tif (!is_dir(TMP . 'src')) {\n\t\t\tmkdir(TMP . 'src', 0770, true);\n\t\t}\n\t\t$execPath = TMP . 'src' . DS . 'CustomClass.php';\n\t\tcopy($path, $execPath);\n\n\t\t$annotator->annotate($execPath);\n\n\t\t$content = file_get_contents($execPath);\n\n\t\t$testPath = TEST_FILES . 'Custom' . DS . 'CustomClass.php';\n\t\t$expectedContent = file_get_contents($testPath);\n\t\t$this->assertTextEquals($expectedContent, $content);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('  -> 1 annotation added, 1 annotation removed.', $output);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\ClassAnnotator\n\t */\n\tprotected function _getAnnotatorMock(array $params) {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new ClassAnnotator($this->io, $params);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/CommandAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\CommandAnnotator;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass CommandAnnotatorTest extends TestCase {\n\n\tuse DiffHelperTrait;\n\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t];\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Command/MyCommand.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Command/MyCommand.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 2 annotations added.', $output);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\CommandAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getAnnotatorMock(array $params): CommandAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t];\n\n\t\treturn $this->getMockBuilder(CommandAnnotator::class)->onlyMethods(['storeFile'])->setConstructorArgs([$this->io, $params])->getMock();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ComponentAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ComponentAnnotator;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse Shim\\TestSuite\\TestTrait;\n\nclass ComponentAnnotatorTest extends TestCase {\n\n\tuse DiffHelperTrait;\n\tuse TestTrait;\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/Component/MyComponent.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Controller/Component/MyComponent.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 3 annotations added.', $output);\n\t}\n\n\t/**\n\t * Note that property always needs $ in front of it: $Prop instead of Prop.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateWithExistingDocBlock() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/Component/MyOtherComponent.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Controller/Component/MyOtherComponent.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\ComponentAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getAnnotatorMock(array $params): ComponentAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t];\n\n\t\treturn $this->getMockBuilder(ComponentAnnotator::class)->onlyMethods(['storeFile'])->setConstructorArgs([$this->io, $params])->getMock();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithControllerUsage() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/Component/MyControllerComponent.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Controller/Component/MyControllerComponent.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ControllerAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ControllerAnnotator;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass ControllerAnnotatorTest extends TestCase {\n\n\tuse DiffHelperTrait;\n\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t];\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/FoosController.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Controller/FoosController.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithCustomModelAndLoadModel() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/BarController.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Controller/BarController.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 3 annotations added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithAppController() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/AppController.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Controller/AppController.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\ControllerAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getAnnotatorMock(array $params): ControllerAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t];\n\n\t\treturn $this->getMockBuilder(ControllerAnnotator::class)->onlyMethods(['storeFile'])->setConstructorArgs([$this->io, $params])->getMock();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithPluginController() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/HousesController.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())\n\t\t\t->method('storeFile')\n\t\t\t->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . '/plugins/Controllers/src/Controller/HousesController.php';\n\t\t$annotator->setConfig(ControllerAnnotator::CONFIG_PLUGIN, 'Controllers');\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithPluginControllerExplicit() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/WindowsController.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())\n\t\t\t->method('storeFile')\n\t\t\t->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . '/plugins/Controllers/src/Controller/WindowsController.php';\n\t\t$annotator->setConfig(ControllerAnnotator::CONFIG_PLUGIN, 'Controllers');\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithPluginControllerNoModel() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$annotator->expects($this->never())\n\t\t\t->method('storeFile');\n\n\t\t$path = APP_ROOT . '/plugins/Controllers/src/Controller/GenericController.php';\n\t\t$annotator->setConfig(ControllerAnnotator::CONFIG_PLUGIN, 'Awesome');\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextNotContains('annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithDynamicProperties() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/DynamicPropertiesController.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Controller/DynamicPropertiesController.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithDynamicPropertiesExistingDocblock() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Controller/DynamicPropertiesExistingDocblockController.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Controller/DynamicPropertiesExistingDocblockController.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/DiffHelperTrait.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse SebastianBergmann\\Diff\\Differ;\nuse SebastianBergmann\\Diff\\Output\\DiffOnlyOutputBuilder;\n\ntrait DiffHelperTrait {\n\n\t/**\n\t * Outputs some debug info for tests.\n\t *\n\t * @param string $expected\n\t * @param string $actual\n\t *\n\t * @return void\n\t */\n\tprotected function _displayDiff($expected, $actual) {\n\t\t$differ = new Differ(new DiffOnlyOutputBuilder());\n\t\t$array = $differ->diffToArray($expected, $actual);\n\n\t\t$begin = null;\n\t\t$end = null;\n\t\tforeach ($array as $key => $row) {\n\t\t\tif ($row[1] === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ($begin === null) {\n\t\t\t\t$begin = $key;\n\t\t\t}\n\t\t\t$end = $key;\n\t\t}\n\t\tif ($begin === null) {\n\t\t\treturn;\n\t\t}\n\t\t$firstLineOfOutput = $begin > 0 ? $begin - 1 : 0;\n\t\t$lastLineOfOutput = count($array) - 1 > $end ? $end + 1 : $end;\n\n\t\t$out = [];\n\t\tfor ($i = $firstLineOfOutput; $i <= $lastLineOfOutput; $i++) {\n\t\t\t$row = $array[$i];\n\t\t\t$char = ' ';\n\t\t\tif ($row[1] === 1) {\n\t\t\t\t$char = '+';\n\t\t\t\t$out[] = $char . $row[0];\n\t\t\t} elseif ($row[1] === 2) {\n\t\t\t\t$char = '-';\n\t\t\t\t$out[] = $char . $row[0];\n\t\t\t} else {\n\t\t\t\t$out[] = $char . $row[0];\n\t\t\t}\n\t\t}\n\n\t\techo PHP_EOL . '####### diff #######' . PHP_EOL . (implode(PHP_EOL, $out)) . PHP_EOL . '##### diff end #####' . PHP_EOL;\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/EntityAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Core\\Configure;\nuse Cake\\Database\\Schema\\TableSchema;\nuse Cake\\ORM\\TableRegistry;\nuse Cake\\TestSuite\\TestCase;\nuse Cake\\View\\View;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\EntityAnnotator;\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\View\\Helper\\DocBlockHelper;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse Shim\\TestSuite\\TestTrait;\nuse TestApp\\Model\\Table\\FoosTable;\n\nclass EntityAnnotatorTest extends TestCase {\n\n\tuse DiffHelperTrait;\n\tuse TestTrait;\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\tConfigure::delete('IdeHelper');\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\n\t\t$x = TableRegistry::getTableLocator()->get('IdeHelper.Foos', ['className' => FoosTable::class]);\n\t\t$columns = [\n\t\t\t'id' => [\n\t\t\t\t'type' => 'integer',\n\t\t\t\t'length' => 11,\n\t\t\t\t'unsigned' => false,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'autoIncrement' => true,\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t\t'name' => [\n\t\t\t\t'type' => 'string',\n\t\t\t\t'length' => 100,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t\t'fixed' => null,\n\t\t\t],\n\t\t\t'content' => [\n\t\t\t\t'type' => 'string',\n\t\t\t\t'length' => 100,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t\t'fixed' => null,\n\t\t\t],\n\t\t\t'offer_date' => [\n\t\t\t\t'type' => 'date',\n\t\t\t\t'length' => null,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t\t'created' => [\n\t\t\t\t'type' => 'datetime',\n\t\t\t\t'length' => null,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t\t'modified' => [\n\t\t\t\t'type' => 'datetime',\n\t\t\t\t'length' => null,\n\t\t\t\t'null' => true,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t\t'params' => [\n\t\t\t\t'type' => 'json',\n\t\t\t\t'length' => null,\n\t\t\t\t'null' => true,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t];\n\t\t$schema = new TableSchema('Foos', $columns);\n\t\t$x->setSchema($schema);\n\t\tTableRegistry::getTableLocator()->set('Foos', $x);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuildExtendedEntityPropertyHintTypeMap() {\n\t\t$config = [];\n\t\t$annotator = new EntityAnnotator(new Io(new ConsoleIo()), $config);\n\n\t\tConfigure::write('IdeHelper.typeMap', [\n\t\t\t'custom' => 'array',\n\t\t\t'longtext' => null,\n\t\t]);\n\t\tConfigure::write('IdeHelper.nullableMap', [\n\t\t\t'custom' => false,\n\t\t]);\n\n\t\t$propertySchema = [\n\t\t\t'invalid' => [\n\t\t\t\t'kind' => 'column',\n\t\t\t\t'type' => 'invalid',\n\t\t\t],\n\t\t\t'custom' => [\n\t\t\t\t'kind' => 'column',\n\t\t\t\t'type' => 'custom',\n\t\t\t\t'null' => false,\n\t\t\t],\n\t\t\t'json' => [\n\t\t\t\t'kind' => 'column',\n\t\t\t\t'type' => 'json',\n\t\t\t\t'null' => true,\n\t\t\t],\n\t\t\t'resetted' => [\n\t\t\t\t'kind' => 'column',\n\t\t\t\t'type' => 'longtext',\n\t\t\t],\n\t\t];\n\t\t$helper = new DocBlockHelper(new View());\n\n\t\t/** @uses \\IdeHelper\\Annotator\\EntityAnnotator::buildExtendedEntityPropertyHintTypeMap() */\n\t\t$result = $this->invokeMethod($annotator, 'buildExtendedEntityPropertyHintTypeMap', [$propertySchema, $helper]);\n\t\t$expected = [\n\t\t\t'custom' => 'array',\n\t\t\t'json' => 'array|null',\n\t\t];\n\t\t$this->assertSame($result, $expected);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t/** @var \\TestApp\\Model\\Table\\FoosTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Foos');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/Foo.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Entity/Foo.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 4 annotations added, 1 annotation updated.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithExistingDocBlock() {\n\t\t/** @var \\TestApp\\Model\\Table\\CarsTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Cars');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/Car.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Entity/Car.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 7 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithExistingDocBlockComplex() {\n\t\t/** @var \\TestApp\\Model\\Table\\WheelsTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Wheels');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/Complex/Wheel.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Entity/Complex/Wheel.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added, 3 annotations removed, 1 annotation skipped.', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithVirtualProperties() {\n\t\t/** @var \\TestApp\\Model\\Table\\FoosTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Foos');\n\t\t$Table->hasMany('Wheels');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/Wheel.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Entity/Wheel.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 9 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithVirtualPropertiesReadOnly() {\n\t\t/** @var \\TestApp\\Model\\Table\\FoosTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Foos');\n\t\t$Table->hasMany('Wheels');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/Virtual.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Entity/Virtual.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 10 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithVirtualPropertiesAndReturnTypes() {\n\t\t/** @var \\TestApp\\Model\\Table\\FoosTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Foos');\n\t\t$Table->hasMany('Wheels');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/PHP7/Virtual.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Entity/PHP7/Virtual.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 11 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateHasOne() {\n\t\t/** @var \\Relations\\Model\\Table\\UsersTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Relations.Users');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/Relations/User.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = PLUGINS . 'Relations/src/Model/Entity/User.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = (string)$this->out->output();\n\n\t\t$this->assertTextContains('   -> 4 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateBelongsToRequired() {\n\t\t/** @var \\Relations\\Model\\Table\\UsersTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Relations.Foos');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/Relations/Foo.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = PLUGINS . 'Relations/src/Model/Entity/Foo.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = (string)$this->out->output();\n\n\t\t$this->assertTextContains('   -> 5 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateBelongsToNullable() {\n\t\t/** @var \\Relations\\Model\\Table\\BarsTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Relations.Bars');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/Relations/Bar.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = PLUGINS . 'Relations/src/Model/Entity/Bar.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = (string)$this->out->output();\n\n\t\t$this->assertTextContains('   -> 4 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithGenericUsage() {\n\t\t/** @var \\TestApp\\Model\\Table\\FoosTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Foos');\n\t\t$Table->hasMany('Wheels');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/PHP/Generics.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Entity/PHP/Generics.php';\n\n\t\tConfigure::write('IdeHelper.arrayAsGenerics', true);\n\n\t\t$annotator->annotate($path);\n\n\t\tConfigure::delete('IdeHelper.arrayAsGenerics');\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added, 1 annotation updated', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithDuplicates() {\n\t\t/** @var \\TestApp\\Model\\Table\\FoosTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Foos');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/PHP/Duplicates.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Entity/PHP/Duplicates.php';\n\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation removed', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithComplexType() {\n\t\t/** @var \\TestApp\\Model\\Table\\FoosTable $Table */\n\t\t$Table = TableRegistry::getTableLocator()->get('Foos');\n\n\t\t$schema = $Table->getSchema();\n\t\t$associations = $Table->associations();\n\t\t$annotator = $this->_getAnnotatorMock(['schema' => $schema, 'associations' => $associations]);\n\n\t\t$expectedContent = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", file_get_contents(TEST_FILES . 'Model/Entity/PHP/ComplexType.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->never())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Entity/PHP/ComplexType.php';\n\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation skipped', $output);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\EntityAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getAnnotatorMock(array $params): EntityAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t];\n\n\t\treturn $this->getMockBuilder(EntityAnnotator::class)->onlyMethods(['storeFile'])->setConstructorArgs([$this->io, $params])->getMock();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/HelperAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\HelperAnnotator;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass HelperAnnotatorTest extends TestCase {\n\n\tuse DiffHelperTrait;\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'View/Helper/MyHelper.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'View/Helper/MyHelper.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 3 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotatePreservesCustomMethodAnnotations() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'View/Helper/MyMethodHelper.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'View/Helper/MyMethodHelper.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 2 annotations added', $output);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\HelperAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getAnnotatorMock(array $params): HelperAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t];\n\n\t\treturn $this->getMockBuilder(HelperAnnotator::class)->onlyMethods(['storeFile'])->setConstructorArgs([$this->io, $params])->getMock();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ModelAnnotatorSpecificTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Core\\Configure;\nuse Cake\\Database\\Schema\\TableSchema;\nuse Cake\\ORM\\TableRegistry;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ModelAnnotator;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse TestApp\\Model\\Table\\FoosTable;\n\n/**\n * Test with concreteEntitiesInParam config on.\n */\nclass ModelAnnotatorSpecificTest extends TestCase {\n\n\tuse DiffHelperTrait;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Foos',\n\t\t'plugin.IdeHelper.Wheels',\n\t\t'plugin.IdeHelper.BarBars',\n\t];\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\n\t\tConfigure::write('IdeHelper.assocsAsGenerics', true);\n\t\tConfigure::write('IdeHelper.concreteEntitiesInParam', true);\n\t\tConfigure::write('IdeHelper.genericsInParam', true);\n\t\tConfigure::write('IdeHelper.tableBehaviors', 'mixin');\n\n\t\t$x = TableRegistry::getTableLocator()->get('IdeHelper.Foos', ['className' => FoosTable::class]);\n\t\t$columns = [\n\t\t\t'id' => [\n\t\t\t\t'type' => 'integer',\n\t\t\t\t'length' => 11,\n\t\t\t\t'unsigned' => false,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'autoIncrement' => true,\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t\t'name' => [\n\t\t\t\t'type' => 'string',\n\t\t\t\t'length' => 100,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t\t'fixed' => null,\n\t\t\t],\n\t\t\t'content' => [\n\t\t\t\t'type' => 'string',\n\t\t\t\t'length' => 100,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t\t'fixed' => null,\n\t\t\t],\n\t\t\t'created' => [\n\t\t\t\t'type' => 'datetime',\n\t\t\t\t'length' => null,\n\t\t\t\t'null' => true,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t];\n\t\t$schema = new TableSchema('Foos', $columns);\n\t\t$x->setSchema($schema);\n\t\tTableRegistry::getTableLocator()->set('Foos', $x);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\tConfigure::delete('IdeHelper.assocsAsGenerics');\n\t\tConfigure::delete('IdeHelper.concreteEntitiesInParam');\n\t\tConfigure::delete('IdeHelper.genericsInParam');\n\t\tConfigure::delete('IdeHelper.tableBehaviors');\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\ModelAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getAnnotatorMock(array $params): ModelAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn $this->getMockBuilder(ModelAnnotator::class)->onlyMethods(['storeFile'])->setConstructorArgs([$this->io, $params])->getMock();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSpecific() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/Specific/BarBarsTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/Specific/BarBarsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('  -> 7 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSpecificDetailed() {\n\t\tConfigure::write('IdeHelper.genericsInParam', 'detailed');\n\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/Specific/BarBarsDetailedTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/Specific/BarBarsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('  -> 7 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSpecificExistingMerge() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/Specific/WheelsTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/Specific/WheelsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('  -> 14 annotations added, 1 annotation updated', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSpecificExistingReplace() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/Specific/WheelsExtraTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/Specific/WheelsExtraTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('  -> 1 annotation updated', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSpecificSkip() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/Specific/SkipSomeTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->never())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/Specific/SkipSomeTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertSame('', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSpecificSkipAll() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/Specific/SkipMeTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->never())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/Specific/SkipMeTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertSame('', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSpecificCatchExceptions() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$path = APP . 'Model/Table/Specific/ExceptionsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextNotContains('annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSpecificProtectedParent() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/Specific/BarBarsAbstractTable.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->method('storeFile')\n\t\t\t->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/Specific/BarBarsAbstractTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 17 annotations added', $output);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ModelAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Core\\Configure;\nuse Cake\\Database\\Schema\\TableSchema;\nuse Cake\\ORM\\TableRegistry;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ModelAnnotator;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse TestApp\\Model\\Table\\FoosTable;\n\nclass ModelAnnotatorTest extends TestCase {\n\n\tuse DiffHelperTrait;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Foos',\n\t\t'plugin.IdeHelper.Wheels',\n\t\t'plugin.IdeHelper.BarBars',\n\t];\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\n\t\tConfigure::write('IdeHelper.assocsAsGenerics', true);\n\t\tConfigure::write('IdeHelper.tableBehaviors', true);\n\n\t\t$x = TableRegistry::getTableLocator()->get('IdeHelper.Foos', ['className' => FoosTable::class]);\n\t\t$columns = [\n\t\t\t'id' => [\n\t\t\t\t'type' => 'integer',\n\t\t\t\t'length' => 11,\n\t\t\t\t'unsigned' => false,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'autoIncrement' => true,\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t\t'name' => [\n\t\t\t\t'type' => 'string',\n\t\t\t\t'length' => 100,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t\t'fixed' => null,\n\t\t\t],\n\t\t\t'content' => [\n\t\t\t\t'type' => 'string',\n\t\t\t\t'length' => 100,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t\t'fixed' => null,\n\t\t\t],\n\t\t\t'created' => [\n\t\t\t\t'type' => 'datetime',\n\t\t\t\t'length' => null,\n\t\t\t\t'null' => true,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t\t'params' => [\n\t\t\t\t'type' => 'json',\n\t\t\t\t'length' => null,\n\t\t\t\t'null' => true,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t];\n\t\t$schema = new TableSchema('Foos', $columns);\n\t\t$x->setSchema($schema);\n\t\tTableRegistry::getTableLocator()->set('Foos', $x);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\tConfigure::delete('IdeHelper.assocsAsGenerics');\n\t\tConfigure::delete('IdeHelper.tableEntityQuery');\n\t\tConfigure::delete('IdeHelper.tableBehaviors');\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\ModelAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getAnnotatorMock(array $params): ModelAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn $this->getMockBuilder(ModelAnnotator::class)->onlyMethods(['storeFile'])->setConstructorArgs([$this->io, $params])->getMock();\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\ModelAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getEntityTemplateAnnotatorMock(array $params): ModelAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\t$mock = $this->getMockBuilder(ModelAnnotator::class)\n\t\t\t->onlyMethods(['storeFile', 'supportsEntityTemplate'])\n\t\t\t->setConstructorArgs([$this->io, $params])\n\t\t\t->getMock();\n\t\t$mock->method('supportsEntityTemplate')->willReturn(true);\n\n\t\treturn $mock;\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/BarBarsTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/BarBarsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('  -> 18 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateExistingMerge() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/WheelsTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/WheelsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('  -> 15 annotations added, 1 annotation updated', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateExistingReplace() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/WheelsExtraTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/WheelsExtraTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('  -> 1 annotation updated', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSkip() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/SkipSomeTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->never())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/SkipSomeTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertSame('', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateSkipAll() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/SkipMeTable.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->never())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/SkipMeTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertSame('', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateCatchExceptions() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$path = APP . 'Model/Table/ExceptionsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextNotContains('annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateProtectedParent() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/BarBarsAbstractTable.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->method('storeFile')\n\t\t\t->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/BarBarsAbstractTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 18 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateDetailed() {\n\t\tConfigure::write('IdeHelper.genericsInParam', 'detailed');\n\t\tConfigure::write('IdeHelper.arrayAsGenerics', true);\n\t\tConfigure::write('IdeHelper.objectAsGenerics', true);\n\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/BarBarsDetailedTable.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/BarBarsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\tConfigure::delete('IdeHelper.genericsInParam');\n\t\tConfigure::delete('IdeHelper.arrayAsGenerics');\n\t\tConfigure::delete('IdeHelper.objectAsGenerics');\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 18 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithEntityFindQuery() {\n\t\tConfigure::write('IdeHelper.tableEntityQuery', true);\n\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/BarBarsTable.php'));\n\t\t$expectedContent = str_replace(\n\t\t\t\" * @method \\\\TestApp\\\\Model\\\\Entity\\\\BarBar get(mixed \\$primaryKey, array|string \\$finder = 'all', \\\\Psr\\\\SimpleCache\\\\CacheInterface|string|null \\$cache = null, \\\\Closure|string|null \\$cacheKey = null, mixed ...\\$args)\\n * @method \\\\TestApp\\\\Model\\\\Entity\\\\BarBar findOrCreate(\\\\Cake\\\\ORM\\\\Query\\\\SelectQuery|callable|array \\$search, ?callable \\$callback = null, array \\$options = [])\",\n\t\t\t\" * @method \\\\TestApp\\\\Model\\\\Entity\\\\BarBar get(mixed \\$primaryKey, array|string \\$finder = 'all', \\\\Psr\\\\SimpleCache\\\\CacheInterface|string|null \\$cache = null, \\\\Closure|string|null \\$cacheKey = null, mixed ...\\$args)\\n * @method \\\\Cake\\\\ORM\\\\Query\\\\SelectQuery<\\\\TestApp\\\\Model\\\\Entity\\\\BarBar> find(string \\$type = 'all', mixed ...\\$args)\\n * @method \\\\TestApp\\\\Model\\\\Entity\\\\BarBar findOrCreate(\\\\Cake\\\\ORM\\\\Query\\\\SelectQuery|callable|array \\$search, ?callable \\$callback = null, array \\$options = [])\",\n\t\t\t$expectedContent,\n\t\t);\n\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/BarBarsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 19 annotations added', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotateWithEntityTemplate() {\n\t\t$annotator = $this->_getEntityTemplateAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'Model/Table/BarBarsEntityTemplateTable.php'));\n\t\t$callback = function ($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'Model/Table/BarBarsTable.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('  -> 18 annotations added', $output);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/Template/VariableExtractorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator\\Template;\n\nuse IdeHelper\\Annotator\\Template\\VariableExtractor;\nuse IdeHelper\\Annotator\\Traits\\FileTrait;\nuse PHP_CodeSniffer\\Config;\nuse Shim\\TestSuite\\TestCase;\n\n$composerVendorDir = PLUGIN_ROOT . DS . 'vendor';\n$codesnifferDir = 'squizlabs' . DS . 'php_codesniffer';\n$manualAutoload = $composerVendorDir . DS . $codesnifferDir . DS . 'autoload.php';\nif (!class_exists(Config::class) && file_exists($manualAutoload)) {\n\trequire $manualAutoload;\n}\n\nclass VariableExtractorTest extends TestCase {\n\n\tuse FileTrait;\n\n\tprotected VariableExtractor $variableExtractor;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->variableExtractor = new VariableExtractor();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testExtract() {\n\t\t$path = APP_ROOT . DS . 'templates' . DS . 'Foos' . DS . 'vars.php';\n\n\t\t$file = $this->getFile($path);\n\n\t\t$result = $this->variableExtractor->extract($file);\n\n\t\t$expected = [\n\t\t\t'obj' => [\n\t\t\t\t'type' => 'object',\n\t\t\t\t'excludeReason' => null,\n\t\t\t],\n\t\t\t'car' => [\n\t\t\t\t'type' => 'object',\n\t\t\t\t'excludeReason' => 'Declared in loop',\n\t\t\t],\n\t\t\t'allCars' => [\n\t\t\t\t'type' => 'object',\n\t\t\t\t'excludeReason' => null,\n\t\t\t],\n\t\t\t'wheel' => [\n\t\t\t\t'type' => 'object',\n\t\t\t\t'excludeReason' => null,\n\t\t\t],\n\t\t\t'date' => [\n\t\t\t\t'type' => 'object',\n\t\t\t\t'excludeReason' => null,\n\t\t\t],\n\t\t\t'i' => [\n\t\t\t\t'type' => null,\n\t\t\t\t'excludeReason' => 'Declared in loop',\n\t\t\t],\n\t\t\t'engine' => [\n\t\t\t\t'type' => null,\n\t\t\t\t'excludeReason' => 'Declared in loop',\n\t\t\t],\n\t\t];\n\t\tforeach ($expected as $name => $data) {\n\t\t\t$this->assertSame($name, $result[$name]['name'], print_r($result[$name], true));\n\t\t\t$this->assertSame($data['type'], $result[$name]['type'], print_r($result[$name], true));\n\t\t\t$this->assertSame($data['excludeReason'], $result[$name]['excludeReason'], print_r($result[$name], true));\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testExtractExceptions() {\n\t\t$content = <<<'PHP'\n<?php\nforeach ($exceptions as $exception) {}\n\ntry {\n} catch (Exception $e) {\n}\nPHP;\n\n\t\t$file = $this->getFile('', $content);\n\n\t\t$result = $this->variableExtractor->extract($file);\n\n\t\t$expected = [\n\t\t\t'exceptions' => [\n\t\t\t\t'excludeReason' => null,\n\t\t\t],\n\t\t\t'exception' => [\n\t\t\t\t'excludeReason' => 'Declared in loop',\n\t\t\t],\n\t\t\t'e' => [\n\t\t\t\t'excludeReason' => 'Try catch',\n\t\t\t],\n\t\t];\n\t\tforeach ($expected as $name => $data) {\n\t\t\t$this->assertSame($data['excludeReason'], $result[$name]['excludeReason'], print_r($result[$name], true));\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testExtractAssignment() {\n\t\t$content = <<<'PHP'\n<?php\nif (strpos($module, '.')) {\n    [$prefix, $moduleName, $suffix] = explode('.', $module);\n}\n//list($x, $y) = [$z, $z]; // We dont support the old syntax yet/anymore\nPHP;\n\n\t\t$file = $this->getFile('', $content);\n\n\t\t$result = $this->variableExtractor->extract($file);\n\n\t\t$expected = [\n\t\t\t'module' => [\n\t\t\t\t'excludeReason' => null,\n\t\t\t],\n\t\t\t'moduleName' => [\n\t\t\t\t'excludeReason' => 'Assignment',\n\t\t\t],\n\t\t\t'prefix' => [\n\t\t\t\t'excludeReason' => 'Assignment',\n\t\t\t],\n\t\t\t'suffix' => [\n\t\t\t\t'excludeReason' => 'Assignment',\n\t\t\t],\n\t\t\t/*\n\t\t\t'x' => [\n\t\t\t\t'excludeReason' => 'Assignment',\n\t\t\t],\n\t\t\t'y' => [\n\t\t\t\t'excludeReason' => 'Assignment',\n\t\t\t],\n\t\t\t'z' => [\n\t\t\t\t'excludeReason' => null,\n\t\t\t],\n\t\t\t*/\n\t\t];\n\t\tforeach ($expected as $name => $data) {\n\t\t\t$this->assertSame($data['excludeReason'], $result[$name]['excludeReason'], print_r($result[$name], true));\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testExtractTypeStringAndArray() {\n\t\t$content = <<<'PHP'\n<?php\necho $x['foo'];\necho $string;\necho $y . 'z' . $z;\n?>\n<?= $str ?>\nPHP;\n\n\t\t$file = $this->getFile('', $content);\n\n\t\t$result = $this->variableExtractor->extract($file);\n\n\t\t$expected = [\n\t\t\t'string' => [\n\t\t\t\t'type' => 'string',\n\t\t\t],\n\t\t\t'x' => [\n\t\t\t\t'type' => 'array',\n\t\t\t],\n\t\t\t'y' => [\n\t\t\t\t'type' => 'string',\n\t\t\t],\n\t\t\t'z' => [\n\t\t\t\t'type' => 'string',\n\t\t\t],\n\t\t\t'str' => [\n\t\t\t\t'type' => 'string',\n\t\t\t],\n\t\t];\n\t\tforeach ($expected as $name => $data) {\n\t\t\t$this->assertSame($data['type'], $result[$name]['type'], print_r($result[$name], true));\n\t\t}\n\t}\n\t/**\n\t * @return void\n\t */\n\tpublic function testExtractFromStrings(): void {\n\t\t$content = <<<'PHP'\n<?php\necho \"<strong>'{$url}'</strong>: $title.\";\nPHP;\n\t\t$file = $this->getFile('', $content);\n\n\t\t$result = $this->variableExtractor->extract($file);\n\n\t\t$this->assertSame(['title', 'url'], array_keys($result));\n\t}\n\n\t/**\n\t * Test that anonymous function parameters are excluded\n\t *\n\t * @return void\n\t */\n\tpublic function testExtractExcludesAnonymousFunctionParameters() {\n\t\t$content = <<<'PHP'\n<?php\n$yourMoodIds = array_map(function($m) { return $m->mood_id; }, $participantMoods ?? []);\n$filtered = array_filter($items, function($item) {\n\treturn $item->active;\n});\nusort($data, function($a, $b) {\n\treturn $a->sort_order <=> $b->sort_order;\n});\n$doubled = array_map(fn($x) => $x * 2, $numbers);\n\n// Test with 'use' clause\n$multiplier = 5;\n$result = array_map(function($n) use ($multiplier) {\n\treturn $n * $multiplier;\n}, $values);\nPHP;\n\n\t\t$file = $this->getFile('', $content);\n\n\t\t$result = $this->variableExtractor->extract($file);\n\n\t\t// Check that regular variables are found with correct exclusion reason\n\t\t$regularVars = ['yourMoodIds', 'participantMoods', 'filtered', 'items', 'data', 'doubled', 'numbers', 'multiplier', 'result', 'values'];\n\t\tforeach ($regularVars as $var) {\n\t\t\t$this->assertArrayHasKey($var, $result, \"Variable \\$$var should be found\");\n\t\t\tif (in_array($var, ['yourMoodIds', 'filtered', 'doubled', 'multiplier', 'result'])) {\n\t\t\t\t$this->assertEquals('Assignment', $result[$var]['excludeReason'], \"Variable \\$$var should be excluded as assignment\");\n\t\t\t} else {\n\t\t\t\t$this->assertNull($result[$var]['excludeReason'], \"Variable \\$$var should not be excluded\");\n\t\t\t}\n\t\t}\n\n\t\t// Check that anonymous function parameters are excluded\n\t\t$functionParams = ['m', 'item', 'a', 'b', 'x', 'n'];\n\t\tforeach ($functionParams as $param) {\n\t\t\t$this->assertArrayHasKey($param, $result, \"Parameter \\$$param should be found\");\n\t\t\t$this->assertEquals('Anonymous function parameter', $result[$param]['excludeReason'], \"Parameter \\$$param should be excluded as anonymous function parameter\");\n\t\t}\n\t}\n\n\t/**\n\t * Test that variables passed to compact() are extracted\n\t *\n\t * @return void\n\t */\n\tpublic function testExtractFromCompact(): void {\n\t\t$content = <<<'PHP'\n<?php\necho $this->element(\n    'residents/dropdowns', compact(\n        'accounts', 'unit_options', 'homes', 'units', 'includeSubmit'\n    )\n);\n$result = compact('foo', \"bar\");\n\n// Method calls should be ignored\n$obj->compact('ignored1');\nSomeClass::compact('ignored2');\n\n// Edge cases: array keys and concatenation should be ignored\ncompact(['key' => 'arrayValue']);\ncompact('concat' . 'enated');\nPHP;\n\n\t\t$file = $this->getFile('', $content);\n\n\t\t$result = $this->variableExtractor->extract($file);\n\n\t\t// All variables from compact() calls should be found\n\t\t$expected = ['accounts', 'unit_options', 'homes', 'units', 'includeSubmit', 'foo', 'bar', 'result', 'obj', 'arrayValue'];\n\t\tforeach ($expected as $var) {\n\t\t\t$this->assertArrayHasKey($var, $result, \"Variable \\$$var should be found from compact()\");\n\t\t}\n\n\t\t// Method call arguments should NOT be found\n\t\t$this->assertArrayNotHasKey('ignored1', $result, 'Method call arguments should not be extracted');\n\t\t$this->assertArrayNotHasKey('ignored2', $result, 'Static method call arguments should not be extracted');\n\n\t\t// Array keys should NOT be found (only values)\n\t\t$this->assertArrayNotHasKey('key', $result, 'Array keys should not be extracted');\n\n\t\t// Concatenated strings should NOT be found\n\t\t$this->assertArrayNotHasKey('concat', $result, 'Concatenated strings should not be extracted');\n\t\t$this->assertArrayNotHasKey('enated', $result, 'Concatenated strings should not be extracted');\n\n\t\t// result is assigned, so should be excluded\n\t\t$this->assertEquals('Assignment', $result['result']['excludeReason']);\n\n\t\t// Variables from compact should not have an exclude reason\n\t\t$compactVars = ['accounts', 'unit_options', 'homes', 'units', 'includeSubmit', 'foo', 'bar'];\n\t\tforeach ($compactVars as $var) {\n\t\t\t$this->assertNull($result[$var]['excludeReason'], \"Variable \\$$var from compact() should not be excluded\");\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/TemplateAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Core\\Configure;\nuse Cake\\Database\\Schema\\TableSchema;\nuse Cake\\ORM\\TableRegistry;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\TemplateAnnotator;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse Shim\\TestSuite\\TestTrait;\nuse TestApp\\Model\\Table\\FoosTable;\n\nclass TemplateAnnotatorTest extends TestCase {\n\n\tuse DiffHelperTrait;\n\tuse TestTrait;\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\n\t\t$x = TableRegistry::getTableLocator()->get('IdeHelper.Foos', ['className' => FoosTable::class]);\n\t\t$columns = [\n\t\t\t'id' => [\n\t\t\t\t'type' => 'integer',\n\t\t\t\t'length' => 11,\n\t\t\t\t'unsigned' => false,\n\t\t\t\t'null' => false,\n\t\t\t\t'default' => null,\n\t\t\t\t'comment' => '',\n\t\t\t\t'autoIncrement' => true,\n\t\t\t\t'baseType' => null,\n\t\t\t\t'precision' => null,\n\t\t\t],\n\t\t];\n\t\t$schema = new TableSchema('Foos', $columns);\n\t\t$x->setSchema($schema);\n\t\tTableRegistry::getTableLocator()->set('Foos', $x);\n\n\t\tConfigure::delete('IdeHelper');\n\t\tConfigure::write('IdeHelper.preemptive', true);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tConfigure::delete('IdeHelper');\n\n\t\tparent::tearDown();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testGetVariableAnnotations() {\n\t\tConfigure::write('IdeHelper.autoCollect', function(array $variable) {\n\t\t\tif ($variable['name'] === 'date') {\n\t\t\t\treturn 'Cake\\I18n\\DateTime';\n\t\t\t}\n\n\t\t\treturn 'mixed';\n\t\t});\n\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$variable = [\n\t\t\t'name' => 'date',\n\t\t\t'type' => 'object',\n\t\t];\n\t\t/** @uses \\IdeHelper\\Annotator\\TemplateAnnotator::_getVariableAnnotation() */\n\t\t$result = $this->invokeMethod($annotator, 'getVariableAnnotation', [$variable]);\n\t\t$this->assertSame('@var Cake\\I18n\\DateTime $date', (string)$result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testNeedsViewAnnotation() {\n\t\tConfigure::write('IdeHelper.preemptive', false);\n\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$content = '';\n\t\t/** @uses \\IdeHelper\\Annotator\\TemplateAnnotator::_needsViewAnnotation() */\n\t\t$result = $this->invokeMethod($annotator, 'needsViewAnnotation', [$content]);\n\t\t$this->assertFalse($result);\n\n\t\t$content = 'Foo Bar';\n\t\t/** @uses \\IdeHelper\\Annotator\\TemplateAnnotator::_needsViewAnnotation() */\n\t\t$result = $this->invokeMethod($annotator, 'needsViewAnnotation', [$content]);\n\t\t$this->assertFalse($result);\n\n\t\t$content = 'Foo <?php echo $this->Foo->bar(); ?>';\n\t\t/** @uses \\IdeHelper\\Annotator\\TemplateAnnotator::_needsViewAnnotation() */\n\t\t$result = $this->invokeMethod($annotator, 'needsViewAnnotation', [$content]);\n\t\t$this->assertTrue($result);\n\n\t\t$content = 'Foo <?= $x; ?>';\n\t\t/** @uses \\IdeHelper\\Annotator\\TemplateAnnotator::_needsViewAnnotation() */\n\t\t$result = $this->invokeMethod($annotator, 'needsViewAnnotation', [$content]);\n\t\t$this->assertTrue($result);\n\t}\n\n\t/**\n\t * Tests create() parsing part and creating a new PHP tag in first line.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/edit.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/edit.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 2 annotations added.', $output);\n\t}\n\n\t/**\n\t * Tests loop and entity->field, as well as writing into an existing PHP tag.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateLoop() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/loop.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/loop.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 3 annotations added.', $output);\n\t}\n\n\t/**\n\t * Tests loop and entity->field, as well as writing into an existing PHP tag.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotatePhpLine() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/phpline.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/phpline.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 3 annotations added.', $output);\n\t}\n\n\t/**\n\t * Tests merging with existing PHP tag and doc block.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateExistingBasic() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/existing.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/existing.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 2 annotations added.', $output);\n\t}\n\n\t/**\n\t * Tests merging with existing PHP tag and doc block and replacing outdated annotations.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateExistingOutdated() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/outdated.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/outdated.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 2 annotations updated, 1 annotation removed, 1 annotation skipped.', $output);\n\t}\n\n\t/**\n\t * Tests merging with existing PHP tag and doc block - PHP strict_types mode.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateExistingStrict() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/existing_strict.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/existing_strict.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * Tests with empty template\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateEmptyPreemptive() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/empty.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/empty.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * Tests with template variables.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateVars() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/vars.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/vars.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 6 annotations added.', $output);\n\t}\n\n\t/**\n\t * Tests with empty template\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateEmpty() {\n\t\tConfigure::write('IdeHelper.preemptive', false);\n\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$callback = function($value) {\n\t\t};\n\t\t$annotator->expects($this->never())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/empty.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextEquals('', $output);\n\t}\n\n\t/**\n\t * Tests merging with existing inline doc block.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateInline() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/inline.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/inline.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * Tests that a docblock with a following inline one works.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateWithFollowingInline() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/following_inline.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/following_inline.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * Tests that a docblock with arrays in different types, e.g. shape.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateWithShapedArray() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/array.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/array.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * Tests that a multiline array is parsed completly.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateWithMultilineArray() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/multiline.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/multiline.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * Tests that anonymous function parameters are excluded from annotations.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateWithAnonymousFunctions() {\n\t\tConfigure::write('IdeHelper.autoCollect', 'mixed');\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedVariables = [\n\t\t\t'$this',\n\t\t\t'$participantMoods',\n\t\t\t'$yourMoodIds',\n\t\t\t'$items',\n\t\t\t'$filtered',\n\t\t\t'$data',\n\t\t\t'$numbers',\n\t\t\t'$doubled',\n\t\t\t'$multiplier',\n\t\t\t'$values',\n\t\t\t'$result',\n\t\t];\n\n\t\t// Variables that should NOT get annotations (anonymous function parameters)\n\t\t$excludedVariables = ['$m', '$item', '$a', '$b', '$x', '$n'];\n\t\t// Note: $id is excluded too, but it's a foreach loop variable, not an anonymous function parameter\n\n\t\t$callback = function($value) use ($expectedVariables, $excludedVariables) {\n\t\t\t// Extract just the PHPDoc block\n\t\t\tif (preg_match('/\\/\\*\\*(.*?)\\*\\//s', $value, $matches)) {\n\t\t\t\t$docBlock = $matches[1];\n\n\t\t\t\tforeach ($excludedVariables as $var) {\n\t\t\t\t\t// Check if the variable appears in an @var annotation in the doc block\n\t\t\t\t\tif (preg_match('/@var\\s+[^\\s]+\\s+\\\\' . preg_quote($var, '/') . '/', $docBlock)) {\n\t\t\t\t\t\t$this->fail(\"Variable {$var} should not have an annotation (it's an anonymous function parameter)\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true;\n\t\t};\n\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/anonymous.php';\n\t\t$annotator->annotate($path);\n\t}\n\n\t/**\n\t * Tests that $this inside string interpolation doesn't create duplicate annotations.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotateWithStringInterpolation() {\n\t\tConfigure::write('IdeHelper.autoCollect', 'mixed');\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/string_interpolation.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/string_interpolation.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t// Should only add 1 annotation: $name (not a duplicate $this)\n\t\t$this->assertTextContains('   -> 1 annotation added.', $output);\n\t}\n\n\t/**\n\t * Tests that existing custom view annotations are preserved when the class exists.\n\t *\n\t * @return void\n\t */\n\tpublic function testAnnotatePreservesCustomViewAnnotation() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'templates/custom_view.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP_ROOT . DS . 'templates/Foos/custom_view.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t// Should add 1 annotation ($cars) but skip updating the existing $this annotation\n\t\t$this->assertTextContains('   -> 1 annotation added, 1 annotation skipped.', $output);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\TemplateAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getAnnotatorMock(array $params): TemplateAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t];\n\n\t\treturn $this->getMockBuilder(TemplateAnnotator::class)->onlyMethods(['storeFile'])->setConstructorArgs([$this->io, $params])->getMock();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Annotator/ViewAnnotatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Annotator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Annotator\\ViewAnnotator;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\nuse Shim\\TestSuite\\TestTrait;\n\nclass ViewAnnotatorTest extends TestCase {\n\n\tuse DiffHelperTrait;\n\tuse TestTrait;\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['Shim']);\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAnnotate() {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$expectedContent = str_replace(\"\\r\\n\", \"\\n\", file_get_contents(TEST_FILES . 'View/AppView.php'));\n\t\t$callback = function($value) use ($expectedContent) {\n\t\t\t$value = str_replace([\"\\r\\n\", \"\\r\"], \"\\n\", $value);\n\t\t\tif ($value !== $expectedContent) {\n\t\t\t\t$this->_displayDiff($expectedContent, $value);\n\t\t\t}\n\n\t\t\treturn $value === $expectedContent;\n\t\t};\n\t\t$annotator->expects($this->once())->method('storeFile')->with($this->anything(), $this->callback($callback));\n\n\t\t$path = APP . 'View/AppView.php';\n\t\t$annotator->annotate($path);\n\n\t\t$output = $this->out->output();\n\n\t\t$this->assertTextContains('   -> 4 annotations added', $output);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Annotator\\ViewAnnotator|\\PHPUnit\\Framework\\MockObject\\MockObject\n\t */\n\tprotected function _getAnnotatorMock(array $params): ViewAnnotator {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_REMOVE => true,\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t];\n\n\t\treturn $this->getMockBuilder(ViewAnnotator::class)->onlyMethods(['storeFile'])->setConstructorArgs([$this->io, $params])->getMock();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testGetHelpers(): void {\n\t\t$annotator = $this->_getAnnotatorMock([]);\n\n\t\t$result = $this->invokeMethod($annotator, 'addExtractedHelpers', [[]]);\n\t\t$expected = [\n\t\t\t'My' => 'TestApp\\View\\Helper\\MyHelper',\n\t\t\t'Configure' => 'Shim\\View\\Helper\\ConfigureHelper',\n\t\t];\n\t\t$this->assertEquals($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/CodeCompletion/CodeCompletionGeneratorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\CodeCompletion;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\CodeCompletion\\CodeCompletionGenerator;\nuse IdeHelper\\CodeCompletion\\TaskCollection;\n\nclass CodeCompletionGeneratorTest extends TestCase {\n\n\tprotected CodeCompletionGenerator $generator;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['MyNamespace/MyPlugin', 'Shim']);\n\t\t$taskCollection = new TaskCollection();\n\t\t$this->generator = new CodeCompletionGenerator($taskCollection);\n\n\t\t$file = TMP . 'CodeCompletionCakeORM.php';\n\t\tif (file_exists($file)) {\n\t\t\tunlink($file);\n\t\t}\n\t\t$file = TMP . 'CodeCompletionCakeORMQuery.php';\n\t\tif (file_exists($file)) {\n\t\t\tunlink($file);\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->generator->generate();\n\n\t\t$expected = [\n\t\t\t'Cake\\Controller',\n\t\t\t'Cake\\ORM',\n\t\t\t'Cake\\ORM\\Query',\n\t\t\t'Cake\\View',\n\t\t];\n\n\t\t$this->assertSame($expected, $result);\n\t\t$this->assertFileExists(TMP . 'CodeCompletionCakeORM.php');\n\t\t$this->assertFileExists(TMP . 'CodeCompletionCakeORMQuery.php');\n\n\t\t$result = file_get_contents(TMP . 'CodeCompletionCakeORM.php');\n\n\t\t$expected = <<<'TXT'\n<?php\nnamespace Cake\\ORM;\n\n/**\n * Only for code completion - regenerate using `bin/cake code_completion generate`.\n */\nabstract class BehaviorRegistry extends \\Cake\\Core\\ObjectRegistry {\n\n\t/**\n\t * MyNamespace/MyPlugin.My behavior.\n\t *\n\t * @var \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n\t */\n\tpublic $My;\n\n\t/**\n\t * Shim.Nullable behavior.\n\t *\n\t * @var \\Shim\\Model\\Behavior\\NullableBehavior\n\t */\n\tpublic $Nullable;\n\n}\n\nuse ArrayObject;\nuse Cake\\Datasource\\EntityInterface;\nuse Cake\\Event\\EventInterface;\nuse Cake\\ORM\\Query\\SelectQuery;\nuse Cake\\ORM\\RulesChecker;\nuse Cake\\Validation\\Validator;\n\nif (false) {\n\tclass Table {\n\t\tpublic function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void {}\n\t\tpublic function afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, bool $primary): void {}\n\t\tpublic function buildValidator(EventInterface $event, Validator $validator, string $name): void {}\n\t\tpublic function buildRules(RulesChecker $rules): RulesChecker { return $rules; }\n\t\tpublic function beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation): void {}\n\t\tpublic function afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation): void {}\n\t\tpublic function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t}\n\n\tclass Behavior {\n\t\tpublic function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void {}\n\t\tpublic function afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, bool $primary): void {}\n\t\tpublic function buildValidator(EventInterface $event, Validator $validator, string $name): void {}\n\t\tpublic function buildRules(RulesChecker $rules): RulesChecker { return $rules; }\n\t\tpublic function beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation): void {}\n\t\tpublic function afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation): void {}\n\t\tpublic function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t}\n}\n\nTXT;\n\n\t\t$this->assertTextEquals($expected, $result);\n\n\t\t$result = file_get_contents(TMP . 'CodeCompletionCakeORMQuery.php');\n\n\t\t$expected = <<<'TXT'\n<?php\nnamespace Cake\\ORM\\Query;\n\n/**\n * Only for code completion - regenerate using `bin/cake code_completion generate`.\n */\nuse Cake\\Database\\ExpressionInterface;\nuse Cake\\Datasource\\ResultSetInterface;\nuse Closure;\nuse Psr\\SimpleCache\\CacheInterface;\n\nif (false) {\n\t/**\n\t * @template TSubject\n\t */\n\tclass SelectQuery {\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function find(string $finder, mixed ...$args) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function where(\n\t\t\tExpressionInterface|Closure|array|string|null $conditions = null,\n\t\t\tarray $types = [],\n\t\t\tbool $overwrite = false,\n\t\t) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function andWhere($conditions, array $types = []) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function matching(string $assoc, ?Closure $builder = null) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function leftJoinWith(string $assoc, ?Closure $builder = null) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function innerJoinWith(string $assoc, ?Closure $builder = null) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function notMatching(string $assoc, ?Closure $builder = null) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function contain(mixed $associations, Closure|bool $override = false) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function clearContain() {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function cache(Closure|string|false $key, CacheInterface|string $config = 'default') {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function groupBy(ExpressionInterface|array|string $fields, bool $overwrite = false) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function orderBy(ExpressionInterface|Closure|array|string $fields, bool $overwrite = false) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function enableAutoFields(bool $value = true) {}\n\n\t\t/**\n\t\t * @return static<TSubject>\n\t\t */\n\t\tpublic function disableAutoFields() {}\n\n\t\t/**\n\t\t * @return static<array<string,mixed>>\n\t\t */\n\t\tpublic function disableHydration() {}\n\n\t\t/**\n\t\t * @return ResultSetInterface<array-key, TSubject>\n\t\t */\n\t\tpublic function all() {}\n\n\t\t/**\n\t\t * @return TSubject|null\n\t\t */\n\t\tpublic function first() {}\n\n\t\t/**\n\t\t * @return TSubject\n\t\t */\n\t\tpublic function firstOrFail() {}\n\n\t\t/**\n\t\t * @return array<TSubject>\n\t\t */\n\t\tpublic function toArray() {}\n\t}\n}\n\nTXT;\n\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/CodeCompletion/Task/BehaviorTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\CodeCompletion\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\CodeCompletion\\Task\\BehaviorTask;\n\nclass BehaviorTaskTest extends TestCase {\n\n\tprotected BehaviorTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['MyNamespace/MyPlugin', 'Shim']);\n\t\t$this->task = new BehaviorTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->create();\n\n\t\t$expected = <<<'TXT'\nabstract class BehaviorRegistry extends \\Cake\\Core\\ObjectRegistry {\n\n\t/**\n\t * MyNamespace/MyPlugin.My behavior.\n\t *\n\t * @var \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n\t */\n\tpublic $My;\n\n\t/**\n\t * Shim.Nullable behavior.\n\t *\n\t * @var \\Shim\\Model\\Behavior\\NullableBehavior\n\t */\n\tpublic $Nullable;\n\n}\n\nTXT;\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/CodeCompletion/Task/ControllerEventsTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\CodeCompletion\\Task;\n\nuse Cake\\Core\\Configure;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\CodeCompletion\\Task\\ControllerEventsTask;\n\nclass ControllerEventsTaskTest extends TestCase {\n\n\tprotected ControllerEventsTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new ControllerEventsTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollectLegacy(): void {\n\t\tConfigure::write('IdeHelper.codeCompletionReturnType', false);\n\n\t\t$result = $this->task->create();\n\n\t\t$expected = <<<'TXT'\n\nuse Cake\\Event\\EventInterface;\nuse Cake\\Http\\Response;\nuse Psr\\Http\\Message\\UriInterface;\n\nif (false) {\n\tclass Controller {\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function startup(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function beforeFilter(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function beforeRender(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function afterFilter(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function shutdown(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t * @param \\Psr\\Http\\Message\\UriInterface|array|string $url\n\t\t * @param \\Cake\\Http\\Response $response\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function beforeRedirect(EventInterface $event, UriInterface|array|string $url, Response $response) {\n\t\t}\n\t}\n\n\tclass Component {\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function startup(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function beforeFilter(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function beforeRender(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function afterFilter(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function shutdown(EventInterface $event) {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t * @param \\Psr\\Http\\Message\\UriInterface|array|string $url\n\t\t * @param \\Cake\\Http\\Response $response\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function beforeRedirect(EventInterface $event, UriInterface|array|string $url, Response $response) {\n\t\t}\n\t}\n}\n\nTXT;\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\tConfigure::write('IdeHelper.codeCompletionReturnType', true);\n\n\t\t$result = $this->task->create();\n\n\t\t$expected = <<<'TXT'\n\nuse Cake\\Event\\EventInterface;\nuse Cake\\Http\\Response;\nuse Psr\\Http\\Message\\UriInterface;\n\nif (false) {\n\tclass Controller {\n\t\tpublic function startup(EventInterface $event): void {\n\t\t}\n\n\t\tpublic function beforeFilter(EventInterface $event): void {\n\t\t}\n\n\t\tpublic function beforeRender(EventInterface $event): void {\n\t\t}\n\n\t\tpublic function afterFilter(EventInterface $event): void {\n\t\t}\n\n\t\tpublic function shutdown(EventInterface $event): void {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t * @param \\Psr\\Http\\Message\\UriInterface|array|string $url\n\t\t * @param \\Cake\\Http\\Response $response\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function beforeRedirect(EventInterface $event, UriInterface|array|string $url, Response $response): void {\n\t\t}\n\t}\n\n\tclass Component {\n\t\tpublic function startup(EventInterface $event): void {\n\t\t}\n\n\t\tpublic function beforeFilter(EventInterface $event): void {\n\t\t}\n\n\t\tpublic function beforeRender(EventInterface $event): void {\n\t\t}\n\n\t\tpublic function afterFilter(EventInterface $event): void {\n\t\t}\n\n\t\tpublic function shutdown(EventInterface $event): void {\n\t\t}\n\n\t\t/**\n\t\t * @param \\Cake\\Event\\EventInterface $event\n\t\t * @param \\Psr\\Http\\Message\\UriInterface|array|string $url\n\t\t * @param \\Cake\\Http\\Response $response\n\t\t *\n\t\t * @return void\n\t\t */\n\t\tpublic function beforeRedirect(EventInterface $event, UriInterface|array|string $url, Response $response): void {\n\t\t}\n\t}\n}\n\nTXT;\n\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/CodeCompletion/Task/ModelEventsTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\CodeCompletion\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\CodeCompletion\\Task\\ModelEventsTask;\n\nclass ModelEventsTaskTest extends TestCase {\n\n\tprotected ModelEventsTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new ModelEventsTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->create();\n\n\t\t$expected = <<<'TXT'\n\nuse ArrayObject;\nuse Cake\\Datasource\\EntityInterface;\nuse Cake\\Event\\EventInterface;\nuse Cake\\ORM\\Query\\SelectQuery;\nuse Cake\\ORM\\RulesChecker;\nuse Cake\\Validation\\Validator;\n\nif (false) {\n\tclass Table {\n\t\tpublic function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void {}\n\t\tpublic function afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, bool $primary): void {}\n\t\tpublic function buildValidator(EventInterface $event, Validator $validator, string $name): void {}\n\t\tpublic function buildRules(RulesChecker $rules): RulesChecker { return $rules; }\n\t\tpublic function beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation): void {}\n\t\tpublic function afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation): void {}\n\t\tpublic function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t}\n\n\tclass Behavior {\n\t\tpublic function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void {}\n\t\tpublic function afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, bool $primary): void {}\n\t\tpublic function buildValidator(EventInterface $event, Validator $validator, string $name): void {}\n\t\tpublic function buildRules(RulesChecker $rules): RulesChecker { return $rules; }\n\t\tpublic function beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, string $operation): void {}\n\t\tpublic function afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, bool $result, string $operation): void {}\n\t\tpublic function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t\tpublic function afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options): void {}\n\t}\n}\n\nTXT;\n\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/CodeCompletion/Task/ViewEventsTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\CodeCompletion\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\CodeCompletion\\Task\\ViewEventsTask;\n\nclass ViewEventsTaskTest extends TestCase {\n\n\tprotected ViewEventsTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new ViewEventsTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->create();\n\n\t\t$expected = <<<'TXT'\n\nuse Cake\\Event\\EventInterface;\n\nif (false) {\n\tclass Helper {\n\t\tpublic function beforeRenderFile(EventInterface $event): void {}\n\t\tpublic function afterRenderFile(EventInterface $event): void {}\n\t\tpublic function beforeRender(EventInterface $event): void {}\n\t\tpublic function afterRender(EventInterface $event): void {}\n\t\tpublic function beforeLayout(EventInterface $event): void {}\n\t\tpublic function afterLayout(EventInterface $event): void {}\n\t}\n}\n\nTXT;\n\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/CodeCompletion/TaskCollectionTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\CodeCompletion;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\CodeCompletion\\TaskCollection;\n\nclass TaskCollectionTest extends TestCase {\n\n\tprotected TaskCollection $taskCollection;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->taskCollection = new TaskCollection();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testTasks() {\n\t\t$result = $this->taskCollection->tasks();\n\n\t\t$this->assertNotEmpty($result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Command/Annotate/ClassesCommandPathAwareTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Command\\Annotate;\n\nuse Cake\\Console\\TestSuite\\ConsoleIntegrationTestTrait;\nuse Cake\\Core\\Configure;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Test\\TestCase\\Command\\Annotate\\Fixture\\SecondTestPathAwareAnnotatorTask;\nuse IdeHelper\\Test\\TestCase\\Command\\Annotate\\Fixture\\TestPathAwareAnnotatorTask;\n\nclass ClassesCommandPathAwareTest extends TestCase {\n\n\tuse ConsoleIntegrationTestTrait;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $createdFiles = [];\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['IdeHelper', 'Awesome']);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tConfigure::delete('IdeHelper.classAnnotatorTasks');\n\t\tforeach ($this->createdFiles as $path) {\n\t\t\t@unlink($path);\n\t\t}\n\t\t$this->createdFiles = [];\n\t\t@rmdir(ROOT . DS . 'tests' . DS . 'fixtures-pathaware');\n\t\t@rmdir(ROOT . DS . 'tests' . DS);\n\t\t@rmdir(PLUGINS . 'Awesome' . DS . 'tests' . DS . 'fixtures-pathaware');\n\t\t@rmdir(PLUGINS . 'Awesome' . DS . 'tests');\n\t\tparent::tearDown();\n\t}\n\n\t/**\n\t * @return string\n\t */\n\tprotected function placePluginFixtureFile(): string {\n\t\t$dir = PLUGINS . 'Awesome' . DS . 'tests' . DS . 'fixtures-pathaware' . DS;\n\t\tif (!is_dir($dir)) {\n\t\t\tmkdir($dir, 0o777, true);\n\t\t}\n\t\t$path = $dir . 'PluginScannedClass.php';\n\t\tfile_put_contents($path, \"<?php\\nclass PluginScannedClass {}\\n\");\n\t\t$this->createdFiles[] = $path;\n\n\t\treturn $path;\n\t}\n\n\t/**\n\t * Drop a tiny PHP class into the directory the fixture task declares so\n\t * `_classes()` will visit it and emit verbose-mode output we can match.\n\t *\n\t * @return string\n\t */\n\tprotected function placeFixtureFile(): string {\n\t\t$dir = ROOT . DS . 'tests' . DS . 'fixtures-pathaware' . DS;\n\t\tif (!is_dir($dir)) {\n\t\t\tmkdir($dir, 0o777, true);\n\t\t}\n\t\t$path = $dir . 'CustomScannedClass.php';\n\t\tfile_put_contents($path, \"<?php\\nclass CustomScannedClass {}\\n\");\n\t\t$this->createdFiles[] = $path;\n\n\t\treturn $path;\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testPathAwareTaskDirectoryIsWalked(): void {\n\t\t$this->placeFixtureFile();\n\n\t\tConfigure::write('IdeHelper.classAnnotatorTasks', [\n\t\t\tTestPathAwareAnnotatorTask::class => TestPathAwareAnnotatorTask::class,\n\t\t]);\n\n\t\t$this->exec('annotate classes -d -v');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains('tests' . DS . 'fixtures-pathaware');\n\t\t$this->assertOutputContains('CustomScannedClass');\n\t}\n\n\t/**\n\t * Plugin-mode integration: with `-p PluginName`, scanPaths() is resolved\n\t * relative to the plugin's root, so the fixture directory inside the\n\t * plugin is reached even though the same path inside the app is empty.\n\t *\n\t * @return void\n\t */\n\tpublic function testPathAwareTaskWalksPluginPathInPluginMode(): void {\n\t\t$this->placePluginFixtureFile();\n\n\t\tConfigure::write('IdeHelper.classAnnotatorTasks', [\n\t\t\tTestPathAwareAnnotatorTask::class => TestPathAwareAnnotatorTask::class,\n\t\t]);\n\n\t\t$this->exec('annotate classes -p Awesome -d -v');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains('fixtures-pathaware');\n\t\t$this->assertOutputContains('PluginScannedClass');\n\t}\n\n\t/**\n\t * Two registered path-aware tasks declaring the same scan path: the\n\t * directory must be walked once. Verbose output shows the directory\n\t * header on every walk, so we count occurrences and assert exactly 1.\n\t *\n\t * @return void\n\t */\n\tpublic function testDuplicateScanPathsAcrossTasksAreWalkedOnlyOnce(): void {\n\t\t$this->placeFixtureFile();\n\n\t\tConfigure::write('IdeHelper.classAnnotatorTasks', [\n\t\t\tTestPathAwareAnnotatorTask::class => TestPathAwareAnnotatorTask::class,\n\t\t\tSecondTestPathAwareAnnotatorTask::class => SecondTestPathAwareAnnotatorTask::class,\n\t\t]);\n\n\t\t$this->exec('annotate classes -d -v');\n\t\t$this->assertExitSuccess();\n\n\t\t$marker = 'tests' . DS . 'fixtures-pathaware';\n\t\t$count = substr_count($this->_out->messages()[0] ?? '', $marker);\n\t\t// Some Cake versions return messages as array; concat for safety:\n\t\tif (!$count) {\n\t\t\t$count = substr_count(implode(\"\\n\", $this->_out->messages()), $marker);\n\t\t}\n\t\t$this->assertSame(\n\t\t\t1,\n\t\t\t$count,\n\t\t\t\"Directory '{$marker}' should be walked exactly once when two tasks declare it; saw {$count} occurrences.\",\n\t\t);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testNonExistentPathAwareDirectoryIsSkippedSilently(): void {\n\t\tConfigure::write('IdeHelper.classAnnotatorTasks', [\n\t\t\tTestPathAwareAnnotatorTask::class => TestPathAwareAnnotatorTask::class,\n\t\t]);\n\n\t\t$this->exec('annotate classes -d -v');\n\t\t$this->assertExitSuccess();\n\t\t// No fixture file placed; the declared directory does not exist.\n\t\t// Just must not crash; default scan still ran.\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Command/Annotate/ClassesCommandTestCaseWalkTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Command\\Annotate;\n\nuse Cake\\Console\\TestSuite\\ConsoleIntegrationTestTrait;\nuse Cake\\TestSuite\\TestCase;\n\n/**\n * Regression test for the default `tests/TestCase/` walk performed by\n * `bin/cake annotate classes` when `TestClassAnnotatorTask` is in the\n * registered task list. Guards against the walk silently disappearing\n * during refactors.\n */\nclass ClassesCommandTestCaseWalkTest extends TestCase {\n\n\tuse ConsoleIntegrationTestTrait;\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $createdFiles = [];\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $createdDirs = [];\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['IdeHelper']);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tforeach ($this->createdFiles as $path) {\n\t\t\t@unlink($path);\n\t\t}\n\t\tforeach (array_reverse($this->createdDirs) as $dir) {\n\t\t\t@rmdir($dir);\n\t\t}\n\t\t$this->createdFiles = [];\n\t\t$this->createdDirs = [];\n\t\tparent::tearDown();\n\t}\n\n\t/**\n\t * Drop a controller-test class into the app's tests/TestCase/Controller/\n\t * tree so the command's verbose output reports the directory walk.\n\t *\n\t * @return void\n\t */\n\tprotected function placeControllerTestFile(): void {\n\t\t$testCaseDir = ROOT . DS . 'tests' . DS . 'TestCase' . DS;\n\t\t$controllerDir = $testCaseDir . 'Controller' . DS;\n\t\tforeach ([$testCaseDir, $controllerDir] as $dir) {\n\t\t\tif (!is_dir($dir)) {\n\t\t\t\tmkdir($dir, 0o777, true);\n\t\t\t\t$this->createdDirs[] = $dir;\n\t\t\t}\n\t\t}\n\t\t$path = $controllerDir . 'WalkProbeControllerTest.php';\n\t\tfile_put_contents(\n\t\t\t$path,\n\t\t\t\"<?php\\nnamespace App\\\\Test\\\\TestCase\\\\Controller;\\nclass WalkProbeControllerTest {}\\n\",\n\t\t);\n\t\t$this->createdFiles[] = $path;\n\t}\n\n\t/**\n\t * The default test-case scan must reach files inside tests/TestCase/.\n\t * Verbose output emits the directory header for every walked folder, so\n\t * we assert the marker shows up.\n\t *\n\t * @return void\n\t */\n\tpublic function testTestCaseDirectoryIsWalkedByDefault(): void {\n\t\t$this->placeControllerTestFile();\n\n\t\t$this->exec('annotate classes -d -v');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains('tests' . DS . 'TestCase' . DS . 'Controller');\n\t\t$this->assertOutputContains('WalkProbeControllerTest');\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Command/Annotate/Fixture/SecondTestPathAwareAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Command\\Annotate\\Fixture;\n\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\AbstractClassAnnotatorTask;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\PathAwareClassAnnotatorTaskInterface;\n\n/**\n * Second test fixture: declares the same scan path as TestPathAwareAnnotatorTask\n * but in a deliberately different shape — backslash separators and no trailing\n * slash — to exercise both the dedup branch in\n * ClassesCommand::_walkPathAwareTasks() and the path normalization that makes\n * the dedup key stable across separator / trailing-slash style variations.\n */\nclass SecondTestPathAwareAnnotatorTask extends AbstractClassAnnotatorTask implements PathAwareClassAnnotatorTaskInterface {\n\n\t/**\n\t * @return array<string>\n\t */\n\tpublic static function scanPaths(): array {\n\t\treturn ['tests\\\\fixtures-pathaware'];\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path, string $content): bool {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Command/Annotate/Fixture/TestPathAwareAnnotatorTask.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Command\\Annotate\\Fixture;\n\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\AbstractClassAnnotatorTask;\nuse IdeHelper\\Annotator\\ClassAnnotatorTask\\PathAwareClassAnnotatorTaskInterface;\n\n/**\n * Test fixture: a class annotator task that declares a custom scan path\n * via PathAwareClassAnnotatorTaskInterface but does no real annotation\n * work (shouldRun always false).\n */\nclass TestPathAwareAnnotatorTask extends AbstractClassAnnotatorTask implements PathAwareClassAnnotatorTaskInterface {\n\n\t/**\n\t * @return array<string>\n\t */\n\tpublic static function scanPaths(): array {\n\t\treturn ['tests/fixtures-pathaware/'];\n\t}\n\n\t/**\n\t * @param string $path\n\t * @param string $content\n\t * @return bool\n\t */\n\tpublic function shouldRun(string $path, string $content): bool {\n\t\treturn false;\n\t}\n\n\t/**\n\t * @param string $path\n\t * @return bool\n\t */\n\tpublic function annotate(string $path): bool {\n\t\treturn false;\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Command/AnnotateCommandTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Command;\n\nuse Cake\\Console\\TestSuite\\ConsoleIntegrationTestTrait;\nuse Cake\\Core\\Configure;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Command\\AnnotateCommand;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nclass AnnotateCommandTest extends TestCase {\n\n\tuse ConsoleIntegrationTestTrait;\n\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t\t'plugin.IdeHelper.Houses',\n\t\t'plugin.IdeHelper.Windows',\n\t];\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\tif (!is_dir(LOGS)) {\n\t\t\tmkdir(LOGS, 0770, true);\n\t\t}\n\t\t$this->loadPlugins(['IdeHelper']);\n\n\t\tConfigure::write('IdeHelper.assocsAsGenerics', true);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\tConfigure::delete('IdeHelper.assocsAsGenerics');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testModels(): void {\n\t\t$this->exec('annotate models -d -v -r');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains(' annotations added');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testViews(): void {\n\t\t$this->exec('annotate view -d -v -r');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains(' annotations added');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testHelpers(): void {\n\t\t$this->exec('annotate helpers -d -v -r');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains(' annotations added');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testTemplates(): void {\n\t\t$this->exec('annotate templates -d -v -r');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains(' annotations added');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testTemplatesSkipFiles(): void {\n\t\t$this->exec('annotate templates -d -v --file templates/element/deeply/nested.php');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains('-> nested');\n\t\t$this->assertOutputNotContains('-> example');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testControllers() {\n\t\t$this->exec('annotate controllers -d -v -r');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains('BarController');\n\t\t$this->assertOutputContains(' annotations added');\n\t\t$this->assertOutputContains('FoosController');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCommands(): void {\n\t\t$this->exec('annotate commands -d -v -r');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains(' 2 annotations added');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testClasses(): void {\n\t\t$this->exec('annotate classes -d -v');\n\t\t$this->assertExitSuccess();\n\t\t$this->assertOutputContains(' annotation added');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCallbacks(): void {\n\t\t$this->exec('annotate callbacks -d -v');\n\t\t$this->assertExitSuccess();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAll() {\n\t\t$this->exec('annotate all -d -v -r');\n\t\t$this->assertExitSuccess();\n\n\t\t$this->assertOutputContains('[Models]');\n\t\t$this->assertOutputContains('[Controllers]');\n\t\t$this->assertOutputContains('[View]');\n\t\t$this->assertOutputContains('[Templates]');\n\t\t$this->assertOutputContains('[Commands]');\n\t\t$this->assertOutputContains('[Components]');\n\t\t$this->assertOutputContains('[Helpers]');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAllCiModeNoChanges() {\n\t\t$this->exec('annotate all -d -v --ci -p Awesome');\n\t\t$this->assertExitSuccess($this->_out->output());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAllCiModeChanges() {\n\t\t$this->exec('annotate all -d -v --ci');\n\n\t\t$this->assertExitCode(AnnotateCommand::CODE_CHANGES, $this->_out->output());\n\t}\n\n\t/**\n\t * @return array\n\t */\n\tpublic static function provideSubcommandsForCiModeTest() {\n\t\treturn [\n\t\t\t'models' => ['models'],\n\t\t\t'view' => ['view'],\n\t\t\t'helpers' => ['helpers'],\n\t\t\t'components' => ['components'],\n\t\t\t'templates' => ['templates'],\n\t\t\t'controllers' => ['controllers'],\n\t\t];\n\t}\n\n\t/**\n\t *\n\t * @param string $subcommand The subcommand to be tested\n\t * @return void\n\t */\n\t#[DataProvider('provideSubcommandsForCiModeTest')]\n\tpublic function testIndividualSubcommandCiModeNoChanges(string $subcommand): void {\n\t\t$this->skipIf($subcommand === 'view', 'View does not support the plugin parameter');\n\n\t\t$this->exec('annotate ' . $subcommand . ' -d -v --ci -p Awesome');\n\t\t$this->assertExitSuccess($this->_out->output());\n\t}\n\n\t/**\n\t *\n\t * @param string $subcommand The subcommand to be tested\n\t * @return void\n\t */\n\t#[DataProvider('provideSubcommandsForCiModeTest')]\n\tpublic function testIndividualSubcommandCiModeChanges(string $subcommand): void {\n\t\t$this->exec('annotate ' . $subcommand . ' -d -v --ci');\n\n\t\t$this->assertExitCode(AnnotateCommand::CODE_CHANGES, $this->_out->output());\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Command/GenerateCodeCompletionCommandTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Command;\n\nuse Cake\\Console\\TestSuite\\ConsoleIntegrationTestTrait;\nuse Cake\\TestSuite\\TestCase;\n\nclass GenerateCodeCompletionCommandTest extends TestCase {\n\n\tuse ConsoleIntegrationTestTrait;\n\n\tprotected array $files = [\n\t\tTMP . 'CodeCompletionCakeController.php',\n\t\tTMP . 'CodeCompletionCakeORM.php',\n\t\tTMP . 'CodeCompletionCakeORMQuery.php',\n\t\tTMP . 'CodeCompletionCakeView.php',\n\t];\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\tforeach ($this->files as $file) {\n\t\t\tif (file_exists($file)) {\n\t\t\t\tunlink($file);\n\t\t\t}\n\t\t}\n\t\t$this->loadPlugins(['IdeHelper']);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\tforeach ($this->files as $file) {\n\t\t\tif (file_exists($file)) {\n\t\t\t\tunlink($file);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testGenerate() {\n\t\t$this->exec('generate code_completion');\n\t\t$this->assertOutputContains('CodeCompletion files generated: Cake\\Controller, Cake\\ORM, Cake\\ORM\\Query, Cake\\View');\n\t\t$this->assertExitSuccess();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Command/GeneratePhpstormMetaCommandTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Command;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Console\\TestSuite\\ConsoleIntegrationTestTrait;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Command\\GeneratePhpStormMetaCommand;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass GeneratePhpstormMetaCommandTest extends TestCase {\n\n\tuse ConsoleIntegrationTestTrait;\n\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t];\n\n\tprotected GeneratePhpStormMetaCommand|MockObject $command;\n\n\t/**\n\t * @var \\Shim\\TestSuite\\ConsoleOutput\n\t */\n\tprotected ConsoleOutput $out;\n\n\t/**\n\t * @var \\Shim\\TestSuite\\ConsoleOutput\n\t */\n\tprotected ConsoleOutput $err;\n\n\t/**\n\t * @var \\Cake\\Console\\ConsoleIo\n\t */\n\tprotected ConsoleIo $io;\n\n\tprotected const META_FOLDER = ROOT . DS . '.phpstorm.meta.php' . DS;\n\tprotected const META_FILE = self::META_FOLDER . '.ide-helper.meta.php';\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\tif (!is_dir(LOGS)) {\n\t\t\tmkdir(LOGS, 0770, true);\n\t\t}\n\t\tif (file_exists(static::META_FILE)) {\n\t\t\tunlink(static::META_FILE);\n\t\t}\n\t\tif (is_dir(static::META_FOLDER)) {\n\t\t\trmdir(static::META_FOLDER);\n\t\t}\n\t\t$this->loadPlugins(['IdeHelper']);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\t\tunset($this->command);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testDirExists() {\n\t\t$this->assertFalse(file_exists(static::META_FILE));\n\n\t\t$this->exec('generate phpstorm');\n\t\t$this->assertTrue(file_exists(static::META_FILE));\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testDirExistsDryRun() {\n\t\t$this->assertFalse(file_exists(static::META_FILE));\n\t\t$this->exec('generate phpstorm -d');\n\n\t\t$this->assertFalse(file_exists(static::META_FILE));\n\t\t$this->assertFalse(file_exists(static::META_FOLDER));\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testGenerateDryRun() {\n\t\t$this->exec('generate phpstorm -d');\n\t\t$this->assertOutputContains(' needs updating');\n\n\t\t$this->assertSame(GeneratePhpStormMetaCommand::CODE_CHANGES, $this->_exitCode);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testGenerate() {\n\t\t$this->exec('generate phpstorm');\n\t\t$this->assertOutputContains('Meta file `/.phpstorm.meta.php/.ide-helper.meta.php` generated.');\n\n\t\t$this->exec('generate phpstorm');\n\t\t$this->assertOutputContains('Meta file `/.phpstorm.meta.php/.ide-helper.meta.php` still up to date.');\n\n\t\t$this->assertExitSuccess();\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Command/IlluminateCommandTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Command;\n\nuse Cake\\Console\\TestSuite\\ConsoleIntegrationTestTrait;\nuse Cake\\TestSuite\\TestCase;\n\nclass IlluminateCommandTest extends TestCase {\n\n\tuse ConsoleIntegrationTestTrait;\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIlluminateDryRun() {\n\t\t$this->loadPlugins(['IdeHelper']);\n\t\t$this->exec('illuminate code -d -v');\n\n\t\t$this->assertExitCode(2);\n\t\t$this->assertOutputContains('# /src/Model/Entity/Foo.php');\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Console/IoTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Console;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\Console\\Exception\\StopException;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Console\\Io;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass IoTest extends TestCase {\n\n\tprotected Io $io;\n\n\tprotected ConsoleIo $consoleIo;\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\tif (!is_dir(LOGS)) {\n\t\t\tmkdir(LOGS, 0770, true);\n\t\t}\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$this->consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($this->consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\t\tunset($this->Shell);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testSuccess() {\n\t\t$this->io->success('Foos');\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('Foos', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testInfo() {\n\t\t$this->io->info('Foos');\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('Foos', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testComment() {\n\t\t$this->io->comment('Foos');\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('Foos', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testWarn() {\n\t\t$this->io->warn('Foos');\n\n\t\t$output = $this->err->output();\n\t\t$this->assertTextContains('Foos', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testError() {\n\t\t$this->io->error('Foos');\n\n\t\t$output = $this->err->output();\n\t\t$this->assertTextContains('Foos', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testErr() {\n\t\t$this->io->err('Foos');\n\n\t\t$output = $this->err->output();\n\t\t$this->assertTextContains('Foos', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testVerbose() {\n\t\t$this->consoleIo->level(ConsoleIo::VERBOSE);\n\t\t$this->io->verbose('Foos');\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('Foos', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testQuiet() {\n\t\t$this->consoleIo->level(ConsoleIo::QUIET);\n\t\t$this->io->quiet('Foos');\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('Foos', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testNl() {\n\t\t$output = $this->io->nl();\n\n\t\t$this->assertSame(ConsoleOutput::LF, $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testHr() {\n\t\t$this->io->hr();\n\n\t\t$output = $this->out->output();\n\t\t$this->assertTextContains('----', $output);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAbort() {\n\t\t$this->expectException(StopException::class);\n\n\t\t$this->io->abort('Foos');\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Directive/ExitPointTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Directive;\n\nuse Cake\\Console\\ConsoleIo;\nuse IdeHelper\\Generator\\Directive\\ExitPoint;\nuse Shim\\TestSuite\\TestCase;\n\nclass ExitPointTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$directive = new ExitPoint('\\\\' . ConsoleIo::class . '::abort()');\n\n\t\t$result = $directive->build();\n\t\t$expected = <<<'TXT'\n\texitPoint(\\Cake\\Console\\ConsoleIo::abort());\nTXT;\n\t\t$this->assertSame($expected, $result);\n\t\t$this->assertSame('\\\\' . ConsoleIo::class . '::abort()@exitPoint', $directive->key());\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Directive/ExpectedArgumentsTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Directive;\n\nuse Cake\\ORM\\Table;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse Shim\\TestSuite\\TestCase;\n\nclass ExpectedArgumentsTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$map = [\n\t\t\t'\\Foo\\Bar::class',\n\t\t\t'\"string\"',\n\t\t];\n\t\t$directive = new ExpectedArguments('\\\\' . Table::class . '::addBehavior()', 0, $map);\n\n\t\t$result = $directive->build();\n\t\t$expected = <<<'TXT'\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::addBehavior(),\n\t\t0,\n\t\t\\Foo\\Bar::class,\n\t\t\"string\",\n\t);\nTXT;\n\t\t$this->assertSame($expected, $result);\n\t\t$this->assertSame('\\\\' . Table::class . '::addBehavior()@0@expectedArguments', $directive->key());\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Directive/ExpectedReturnValuesTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Directive;\n\nuse Cake\\ORM\\Table;\nuse IdeHelper\\Generator\\Directive\\ExpectedReturnValues;\nuse Shim\\TestSuite\\TestCase;\n\nclass ExpectedReturnValuesTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$map = [\n\t\t\t'\\Foo\\Bar::class',\n\t\t\t'\"string\"',\n\t\t];\n\t\t$directive = new ExpectedReturnValues('\\\\' . Table::class . '::addBehavior()', $map);\n\n\t\t$result = $directive->build();\n\t\t$expected = <<<'TXT'\n\texpectedReturnValues(\n\t\t\\Cake\\ORM\\Table::addBehavior(),\n\t\t\\Foo\\Bar::class,\n\t\t\"string\",\n\t);\nTXT;\n\t\t$this->assertSame($expected, $result);\n\t\t$this->assertSame('\\\\' . Table::class . '::addBehavior()@expectedReturnValues', $directive->key());\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Directive/OverrideTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Directive;\n\nuse Cake\\ORM\\Table;\nuse IdeHelper\\Generator\\Directive\\Override;\nuse IdeHelper\\ValueObject\\ClassName;\nuse IdeHelper\\ValueObject\\KeyValue;\nuse Shim\\TestSuite\\TestCase;\n\nclass OverrideTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$map = [\n\t\t\t'Tree' => ClassName::create(Table::class),\n\t\t\t'CounterCache' => ClassName::create(Table::class),\n\t\t];\n\t\t$directive = new Override('\\\\' . Table::class . '::addBehavior(0)', $map);\n\n\t\t$result = $directive->build();\n\t\t$expected = <<<'TXT'\n\toverride(\n\t\t\\Cake\\ORM\\Table::addBehavior(0),\n\t\tmap([\n\t\t\t'Tree' => \\Cake\\ORM\\Table::class,\n\t\t\t'CounterCache' => \\Cake\\ORM\\Table::class,\n\t\t]),\n\t);\nTXT;\n\t\t$this->assertSame($expected, $result);\n\t\t$this->assertSame('\\\\' . Table::class . '::addBehavior(0)@override', $directive->key());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuildLiteralKey() {\n\t\t$key = ClassName::create(Table::class);\n\n\t\t$value = ClassName::create(Table::class);\n\t\t$keyValue = KeyValue::create($key, $value);\n\t\t$map = [\n\t\t\t'\\\\' . Table::class . '::class' => $keyValue,\n\t\t];\n\t\t$directive = new Override('\\\\' . Table::class . '::addBehavior(0)', $map);\n\n\t\t$result = $directive->build();\n\t\t$expected = <<<'TXT'\n\toverride(\n\t\t\\Cake\\ORM\\Table::addBehavior(0),\n\t\tmap([\n\t\t\t\\Cake\\ORM\\Table::class => \\Cake\\ORM\\Table::class,\n\t\t]),\n\t);\nTXT;\n\t\t$this->assertSame($expected, $result);\n\t\t$this->assertSame('\\\\' . Table::class . '::addBehavior(0)@override', $directive->key());\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Directive/RegisterArgumentsSetTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Directive;\n\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\ExpectedReturnValues;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse Shim\\TestSuite\\TestCase;\n\nclass RegisterArgumentsSetTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testBuild() {\n\t\t$list = [\n\t\t\t'\\Foo\\Bar::class',\n\t\t\t'\"string\"',\n\t\t];\n\t\t$directive = new RegisterArgumentsSet('foo', $list);\n\n\t\t$result = $directive->build();\n\t\t$expected = <<<'TXT'\n\tregisterArgumentsSet(\n\t\t'foo',\n\t\t\\Foo\\Bar::class,\n\t\t\"string\",\n\t);\nTXT;\n\t\t$this->assertSame($expected, $result);\n\t\t$this->assertSame('foo@registerArgumentsSet', $directive->key());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testToString() {\n\t\t$list = [\n\t\t\t'\\Foo\\Bar::class',\n\t\t];\n\t\t$directive = new RegisterArgumentsSet('fooBar', $list);\n\n\t\t$result = (string)$directive;\n\t\t$this->assertSame('argumentsSet(\\'fooBar\\')', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testSetInsideArguments() {\n\t\t$list = [\n\t\t\t'\\Foo\\Bar::class',\n\t\t\t'\"string\"',\n\t\t];\n\t\t$argumentsSet = new RegisterArgumentsSet('fooBar', $list);\n\n\t\t$list = [\n\t\t\t$argumentsSet,\n\t\t];\n\t\t$directive = new ExpectedArguments('\\My\\Class::someMethod()', 1, $list);\n\n\t\t$result = $directive->build();\n\t\t$expected = <<<'TXT'\n\texpectedArguments(\n\t\t\\My\\Class::someMethod(),\n\t\t1,\n\t\targumentsSet('fooBar'),\n\t);\nTXT;\n\t\t$this->assertSame($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testArgumentsSetInsideReturnValues() {\n\t\t$list = [\n\t\t\t'\\Foo\\Bar::class',\n\t\t\t'\"string\"',\n\t\t];\n\t\t$argumentsSet = new RegisterArgumentsSet('fooBar', $list);\n\n\t\t$list = [\n\t\t\t$argumentsSet,\n\t\t];\n\t\t$directive = new ExpectedReturnValues('\\My\\Class::someMethod()', $list);\n\n\t\t$result = $directive->build();\n\t\t$expected = <<<'TXT'\n\texpectedReturnValues(\n\t\t\\My\\Class::someMethod(),\n\t\targumentsSet('fooBar'),\n\t);\nTXT;\n\t\t$this->assertSame($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/PhpstormGeneratorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator;\n\nuse Cake\\Core\\Configure;\nuse Cake\\Core\\Plugin;\nuse IdeHelper\\Generator\\PhpstormGenerator;\nuse IdeHelper\\Generator\\Task\\EnvTask;\nuse IdeHelper\\Generator\\Task\\FixtureTask;\nuse IdeHelper\\Generator\\TaskCollection;\nuse Shim\\TestSuite\\TestCase;\nuse TestApp\\Generator\\Task\\TestEnvTask;\nuse TestApp\\Generator\\Task\\TestFixtureTask;\n\nclass PhpstormGeneratorTest extends TestCase {\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t];\n\n\tprotected ?PhpstormGenerator $generator = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\t\t$this->loadPlugins(['Awesome', 'Controllers', 'MyNamespace/MyPlugin', 'Relations', 'Shim', 'IdeHelper']);\n\n\t\t$taskCollection = new TaskCollection([\n\t\t\tEnvTask::class => TestEnvTask::class,\n\t\t\tFixtureTask::class => TestFixtureTask::class,\n\t\t]);\n\t\t$this->generator = new PhpstormGenerator($taskCollection);\n\n\t\t$file = TMP . '.meta.php';\n\t\tif (file_exists($file)) {\n\t\t\tunlink($file);\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\t$file = TMP . '.meta.php';\n\t\tif (file_exists($file)) {\n\t\t\tunlink($file);\n\t\t}\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\tConfigure::write('IdeHelper.skipDatabaseTables', ['/^(?!wheels)/']);\n\n\t\t$result = $this->generator->generate();\n\t\tfile_put_contents(TMP . '.meta.php', $result);\n\n\t\t$cakeVersion = Configure::version();\n\t\tif (version_compare($cakeVersion, '5.2.0', '<')) {\n\t\t\t$fileName = '.meta_lowest.php';\n\t\t} elseif (version_compare($cakeVersion, '5.3.0', '<')) {\n\t\t\t$fileName = '.meta_52.php';\n\t\t} else {\n\t\t\t$fileName = '.meta.php';\n\t\t}\n\t\t$file = Plugin::path('IdeHelper') . 'tests' . DS . 'test_files' . DS . 'meta' . DS . 'phpstorm' . DS . $fileName;\n\n\t\t// Trick to let the test file get updated: --debug is for PHPUnit 11+, --cache-result for PHPUnit 10\n\t\tif (!empty($_SERVER['argv']) && (in_array('--debug', $_SERVER['argv'], true) || in_array('--cache-result', $_SERVER['argv'], true))) {\n\t\t\tdebug('Adding results to ' . $fileName);\n\t\t\tfile_put_contents($file, $result);\n\t\t}\n\n\t\t$expected = file_get_contents($file);\n\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/BehaviorTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\BehaviorTask;\n\nclass BehaviorTaskTest extends TestCase {\n\n\tprotected BehaviorTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['Shim']);\n\t\t$this->task = new BehaviorTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(4, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\ORM\\Table::addBehavior()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\n\t\t$expected = '\\'Timestamp\\'';\n\t\t$this->assertSame($expected, (string)$list['Timestamp']);\n\n\t\t$expected = '\\'Shim.Nullable\\'';\n\t\t$this->assertSame($expected, (string)$list['Shim.Nullable']);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\ORM\\Table::removeBehavior()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\n\t\t$expected = '\\'Timestamp\\'';\n\t\t$this->assertSame($expected, (string)$list['Timestamp']);\n\n\t\t$expected = '\\'Nullable\\'';\n\t\t$this->assertSame($expected, (string)$list['Nullable']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/CacheTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\Generator\\Task\\CacheTask;\n\nclass CacheTaskTest extends TestCase {\n\n\tprotected CacheTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new CacheTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(12, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\RegisterArgumentsSet $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertInstanceOf(RegisterArgumentsSet::class, $directive);\n\t\t$this->assertSame(CacheTask::SET_CACHE_ENGINES, $directive->toArray()['set']);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Cache\\Cache::clear()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'argumentsSet(\\'cacheEngines\\')',\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/CellTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\CellTask;\n\nclass CellTaskTest extends TestCase {\n\n\tprotected CellTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new CellTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\View\\CellTrait::cell()', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\n\t\t$expected = '\\TestApp\\View\\Cell\\TestCell::class';\n\t\t$this->assertSame($expected, (string)$map['Test']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/ComponentTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\ComponentTask;\n\nclass ComponentTaskTest extends TestCase {\n\n\tprotected ComponentTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new ComponentTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(2, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Controller\\Controller::loadComponent(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\n\t\t$expected = '\\Cake\\Controller\\Component\\FormProtectionComponent::class';\n\t\t$this->assertSame($expected, (string)$map['FormProtection']);\n\n\t\t$expected = '\\TestApp\\Controller\\Component\\MyOtherComponent::class';\n\t\t$this->assertSame($expected, (string)$map['MyOther']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/ConfigureTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\Generator\\Task\\ConfigureTask;\nuse Shim\\TestSuite\\TestTrait;\n\nclass ConfigureTaskTest extends TestCase {\n\n\tuse TestTrait;\n\n\tprotected ConfigureTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new ConfigureTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(8, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\RegisterArgumentsSet $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertInstanceOf(RegisterArgumentsSet::class, $directive);\n\t\t$this->assertSame(ConfigureTask::SET_CONFIGURE_KEYS, $directive->toArray()['set']);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Core\\Configure::read()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'argumentsSet(\\'configureKeys\\')',\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollectKeys(): void {\n\t\t$result = $this->invokeMethod($this->task, 'collectKeys');\n\n\t\t$this->assertArrayHasKey('App.paths.templates', $result);\n\t\t$this->assertArrayNotHasKey('paths', $result);\n\t\t$this->assertArrayNotHasKey('templates', $result);\n\n\t\t$this->assertSame('\\'debug\\'', (string)$result['debug']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/ConnectionTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse IdeHelper\\Generator\\Task\\ConnectionTask;\nuse Shim\\TestSuite\\TestCase;\n\nclass ConnectionTaskTest extends TestCase {\n\n\tprotected ?ConnectionTask $task = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new ConnectionTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Datasource\\ConnectionManager::get()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'test' => \"'test'\",\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/ConsoleHelperTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\ConsoleHelperTask;\n\nclass ConsoleHelperTaskTest extends TestCase {\n\n\tprotected ConsoleHelperTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new ConsoleHelperTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Console\\ConsoleIo::helper(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\n\t\t$expected = '\\Cake\\Command\\Helper\\ProgressHelper::class';\n\t\t$this->assertSame($expected, (string)$map['Progress']);\n\n\t\t$expected = '\\Cake\\Command\\Helper\\TableHelper::class';\n\t\t$this->assertSame($expected, (string)$map['Table']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/ConsoleTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\ConsoleTask;\n\nclass ConsoleTaskTest extends TestCase {\n\n\tprotected ConsoleTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new ConsoleTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExitPoint $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Console\\ConsoleIo::abort()', $directive->toArray()['method']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/DatabaseTableColumnNameTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\DatabaseTableColumnNameTask;\n\nclass DatabaseTableColumnNameTaskTest extends TestCase {\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t];\n\n\tprotected DatabaseTableColumnNameTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->fetchTable('Cars');\n\t\t$this->fetchTable('Wheels');\n\n\t\t$this->task = new DatabaseTableColumnNameTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\tunset($this->task);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(7, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\RegisterArgumentsSet $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame(DatabaseTableColumnNameTask::SET_COLUMN_NAMES, $directive->toArray()['set']);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Migrations\\Db\\Table::addColumn()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expectedList = [\n\t\t\t'argumentsSet(\\'columnNames\\')',\n\t\t];\n\t\t$this->assertSame($expectedList, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Migrations\\Db\\Table::changeColumn()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\t\t$this->assertSame($expectedList, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Migrations\\Db\\Table::removeColumn()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\t\t$this->assertSame($expectedList, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Migrations\\Db\\Table::renameColumn()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\t\t$this->assertSame($expectedList, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Migrations\\Db\\Table::hasColumn()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\t\t$this->assertSame($expectedList, $list);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/DatabaseTableColumnTypeTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\Core\\Plugin;\nuse Cake\\Database\\Driver\\Mysql;\nuse Cake\\Database\\Driver\\Postgres;\nuse Cake\\Datasource\\ConnectionManager;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Task\\DatabaseTableColumnTypeTask;\nuse TestApp\\Generator\\Task\\TestDatabaseTableColumnTypeTask;\n\nclass DatabaseTableColumnTypeTaskTest extends TestCase {\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t];\n\n\tprotected DatabaseTableColumnTypeTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->fetchTable('Cars');\n\t\t$this->fetchTable('Wheels');\n\n\t\t$this->task = new DatabaseTableColumnTypeTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(3, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\RegisterArgumentsSet $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame(DatabaseTableColumnTypeTask::SET_COLUMN_TYPES, $directive->toArray()['set']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expectedList = [\n\t\t\t'text' => \"'text'\",\n\t\t\t'integer' => \"'integer'\",\n\t\t];\n\t\tforeach ($expectedList as $key => $value) {\n\t\t\t$this->assertArrayHasKey($key, $list);\n\t\t\t$this->assertSame($value, $list[$key]);\n\t\t}\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertInstanceOf(ExpectedArguments::class, $directive);\n\t\t$this->assertSame('\\Migrations\\Db\\Table::addColumn()', $directive->toArray()['method']);\n\n\t\t$list = array_map(function ($value) {\n\t\t\treturn (string)$value;\n\t\t}, $list);\n\n\t\t$expectedList = [\n\t\t\t'text' => \"'text'\",\n\t\t\t'integer' => \"'integer'\",\n\t\t];\n\t\tforeach ($expectedList as $key => $value) {\n\t\t\t$this->assertArrayHasKey($key, $list);\n\t\t\t$this->assertSame($value, $list[$key]);\n\t\t}\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Migrations\\Db\\Table::changeColumn()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expectedList = [\n\t\t\t'argumentsSet(\\'columnTypes\\')',\n\t\t];\n\t\t$this->assertSame($expectedList, $list);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollectPluginLoaded() {\n\t\t$driver = ConnectionManager::get('test')->getDriver();\n\t\t$this->skipIf(!($driver instanceof Mysql || $driver instanceof Postgres), 'Only for Postgres/Mysql');\n\n\t\t$this->assertFalse(Plugin::isLoaded('Migrations'));\n\n\t\t$plugin = Plugin::getCollection()->create('Migrations');\n\t\tPlugin::getCollection()->add($plugin);\n\n\t\t$this->assertTrue(Plugin::isLoaded('Migrations'));\n\n\t\t$this->task = new TestDatabaseTableColumnTypeTask();\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(3, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\RegisterArgumentsSet $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame(DatabaseTableColumnTypeTask::SET_COLUMN_TYPES, $directive->toArray()['set']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\t\t$expectedList = [\n\t\t\t'text' => \"'text'\",\n\t\t\t'integer' => \"'integer'\",\n\t\t];\n\t\tforeach ($expectedList as $key => $value) {\n\t\t\t$this->assertArrayHasKey($key, $list);\n\t\t\t$this->assertSame($value, $list[$key]);\n\t\t}\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Migrations\\Db\\Table::addColumn()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expectedList = [\n\t\t\t'argumentsSet(\\'columnTypes\\')',\n\t\t];\n\t\t$this->assertSame($expectedList, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Migrations\\Db\\Table::changeColumn()', $directive->toArray()['method']);\n\n\t\tPlugin::getCollection()->remove('Migrations');\n\t\t$this->assertFalse(Plugin::isLoaded('Migrations'));\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/DatabaseTableTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\Generator\\Task\\DatabaseTableTask;\n\nclass DatabaseTableTaskTest extends TestCase {\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t];\n\n\tprotected DatabaseTableTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->fetchTable('Cars');\n\t\t$this->fetchTable('Wheels');\n\n\t\t$this->task = new DatabaseTableTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\tunset($this->task);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(5, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\RegisterArgumentsSet $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertInstanceOf(RegisterArgumentsSet::class, $directive);\n\t\t$this->assertSame(DatabaseTableTask::SET_TABLE_NAMES, $directive->toArray()['set']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$expectedList = [\n\t\t\t'cars' => \"'cars'\",\n\t\t\t'wheels' => \"'wheels'\",\n\t\t];\n\t\tforeach ($expectedList as $key => $value) {\n\t\t\t$this->assertArrayHasKey($key, $list);\n\t\t\t$this->assertSame($value, (string)$list[$key]);\n\t\t}\n\n\t\t$expectedMethods = [\n\t\t\t'\\Migrations\\BaseMigration::table()',\n\t\t\t'\\Migrations\\BaseMigration::hasTable()',\n\t\t\t'\\Migrations\\BaseSeed::table()',\n\t\t\t'\\Migrations\\BaseSeed::hasTable()',\n\t\t];\n\n\t\tforeach ($expectedMethods as $expectedMethod) {\n\t\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t\t$directive = array_shift($result);\n\t\t\t$this->assertSame($expectedMethod, $directive->toArray()['method']);\n\n\t\t\t$list = $directive->toArray()['list'];\n\t\t\t$list = array_map(function ($className) {\n\t\t\t\treturn (string)$className;\n\t\t\t}, $list);\n\n\t\t\t$expectedList = [\n\t\t\t\t'argumentsSet(\\'tableNames\\')',\n\t\t\t];\n\t\t\t$this->assertSame($expectedList, $list);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/DatabaseTypeTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\Database\\TypeFactory;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\DatabaseTypeTask;\nuse TestApp\\Database\\Type\\UuidType;\n\nclass DatabaseTypeTaskTest extends TestCase {\n\n\tprotected DatabaseTypeTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new DatabaseTypeTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\tTypeFactory::set('uuid', new UuidType());\n\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(2, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Database\\TypeFactory::build(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\n\t\t$expected = '\\Cake\\Database\\Type\\BinaryType::class';\n\t\t$this->assertSame($expected, (string)$map['binary']);\n\n\t\t$expected = '\\TestApp\\Database\\Type\\UuidType::class';\n\t\t$this->assertSame($expected, (string)$map['uuid']);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Database\\TypeFactory::map()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$this->assertSame('\\'json\\'', (string)$list['json']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/ElementTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\ElementTask;\nuse Shim\\TestSuite\\TestTrait;\n\nclass ElementTaskTest extends TestCase {\n\n\tuse TestTrait;\n\n\tprotected ElementTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['Awesome']);\n\t\t$this->task = new ElementTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\View\\View::element()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expectedMap = [\n\t\t\t'Awesome.pagination' => '\\'Awesome.pagination\\'',\n\t\t\t'deeply/nested' => '\\'deeply/nested\\'',\n\t\t\t'example' => '\\'example\\'',\n\t\t];\n\t\t$this->assertSame($expectedMap, $list);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/EntityTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\Generator\\Task\\EntityTask;\n\nclass EntityTaskTest extends TestCase {\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t];\n\n\tprotected EntityTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['Awesome', 'Controllers', 'MyNamespace/MyPlugin', 'Relations', 'Shim', 'IdeHelper']);\n\t\t$this->task = new EntityTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(99, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\RegisterArgumentsSet $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertInstanceOf(RegisterArgumentsSet::class, $directive);\n\t\t$this->assertStringContainsString(EntityTask::SET_ENTITY_FIELDS, $directive->toArray()['set']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'content' => \"'content'\",\n\t\t\t'created' => \"'created'\",\n\t\t\t'foo' => \"'foo'\",\n\t\t\t'houses' => \"'houses'\",\n\t\t\t'id' => \"'id'\",\n\t\t\t'name' => \"'name'\",\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\n\t\t$directive = array_shift($result);\n\t\t$this->assertInstanceOf(ExpectedArguments::class, $directive);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/EnvTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse IdeHelper\\Generator\\Task\\EnvTask;\nuse Shim\\TestSuite\\TestCase;\n\nclass EnvTaskTest extends TestCase {\n\n\tprotected ?EnvTask $task = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new EnvTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\env()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'HTTP_HOST' => \"'HTTP_HOST'\",\n\t\t\t'REMOTE_ADDR' => \"'REMOTE_ADDR'\",\n\t\t];\n\t\tforeach ($expected as $key => $value) {\n\t\t\t$this->assertSame($value, $list[$key]);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/FixtureTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\FixtureTask;\n\nclass FixtureTaskTest extends TestCase {\n\n\tprotected FixtureTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['MyNamespace/MyPlugin', 'IdeHelper']);\n\t\t$this->task = new FixtureTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\\\' . TestCase::class . '::addFixture()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'app.SmallWindows' => '\\'app.SmallWindows\\'',\n\t\t\t'core.Posts' => '\\'core.Posts\\'',\n\t\t\t'plugin.IdeHelper.Windows' => '\\'plugin.IdeHelper.Windows\\'',\n\t\t\t'plugin.MyNamespace/MyPlugin.Sub/My' => '\\'plugin.MyNamespace/MyPlugin.Sub/My\\'',\n\t\t];\n\t\tforeach ($expected as $key => $value) {\n\t\t\t$this->assertSame($value, $list[$key]);\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/FormHelperTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Task\\FormHelperTask;\n\nclass FormHelperTaskTest extends TestCase {\n\n\t/**\n\t * @var array<string>\n\t */\n\tprotected array $fixtures = [\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.IdeHelper.Wheels',\n\t];\n\n\tprotected FormHelperTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['Awesome', 'Controllers', 'MyNamespace/MyPlugin', 'Relations', 'Shim', 'IdeHelper']);\n\t\t$this->fetchTable('Cars');\n\t\t$this->fetchTable('Wheels');\n\n\t\t$this->task = new FormHelperTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\tunset($this->task);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertInstanceOf(ExpectedArguments::class, $directive);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expectedList = [\n\t\t\t'content' => \"'content'\",\n\t\t\t'created' => \"'created'\",\n\t\t\t'id' => \"'id'\",\n\t\t\t'modified' => \"'modified'\",\n\t\t\t'name' => \"'name'\",\n\t\t\t'params' => \"'params'\",\n\t\t\t'status' => \"'status'\",\n\t\t\t'user_id' => \"'user_id'\",\n\t\t];\n\t\t$this->assertSame($expectedList, $list);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/HelperTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\HelperTask;\n\nclass HelperTaskTest extends TestCase {\n\n\tprotected HelperTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['Shim']);\n\t\t$this->task = new HelperTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(3, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\View\\View::loadHelper(0)', $directive->toArray()['method']);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\View\\View::addHelper(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\n\t\t$expected = '\\Cake\\View\\Helper\\FormHelper::class';\n\t\t$this->assertSame($expected, (string)$map['Form']);\n\n\t\t$expected = '\\Shim\\View\\Helper\\ConfigureHelper::class';\n\t\t$this->assertSame($expected, (string)$map['Shim.Configure']);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\View\\ViewBuilder::addHelper()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\n\t\t$expected = \"'Form'\";\n\t\t$this->assertSame($expected, (string)$list['Form']);\n\n\t\t$expected = \"'Shim.Configure'\";\n\t\t$this->assertSame($expected, (string)$list['Shim.Configure']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/LayoutTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\LayoutTask;\nuse Shim\\TestSuite\\TestTrait;\n\nclass LayoutTaskTest extends TestCase {\n\n\tuse TestTrait;\n\n\tprotected LayoutTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new LayoutTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\View\\ViewBuilder::setLayout()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expectedMap = [\n\t\t\t'ajax' => '\\'ajax\\'',\n\t\t];\n\t\t$this->assertSame($expectedMap, $list);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/MailerTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\MailerTask;\n\nclass MailerTaskTest extends TestCase {\n\n\tprotected MailerTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new MailerTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Mailer\\MailerAwareTrait::getMailer(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\n\t\t$expected = '\\TestApp\\Mailer\\UserMailer::class';\n\t\t$this->assertSame($expected, (string)$map['User']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/ModelTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\ModelTask;\n\nclass ModelTaskTest extends TestCase {\n\n\tprotected ModelTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['Awesome', 'Controllers', 'MyNamespace/MyPlugin', 'Relations']);\n\t\t$this->task = new ModelTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(3, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\ORM\\Locator\\LocatorInterface::get(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\t\t$map = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $map);\n\n\t\t$expectedMap = [\n\t\t\t'Awesome.Houses' => '\\Awesome\\Model\\Table\\HousesTable::class',\n\t\t\t'Awesome.Windows' => '\\Awesome\\Model\\Table\\WindowsTable::class',\n\t\t\t'BarBars' => '\\TestApp\\Model\\Table\\BarBarsTable::class',\n\t\t\t'BarBarsAbstract' => '\\TestApp\\Model\\Table\\BarBarsAbstractTable::class',\n\t\t\t'Callbacks' => '\\TestApp\\Model\\Table\\CallbacksTable::class',\n\t\t\t'Cars' => '\\TestApp\\Model\\Table\\CarsTable::class',\n\t\t\t'Controllers.Houses' => '\\Controllers\\Model\\Table\\HousesTable::class',\n\t\t\t'CustomFinder' => '\\TestApp\\Model\\Table\\CustomFinderTable::class',\n\t\t\t'Exceptions' => '\\TestApp\\Model\\Table\\ExceptionsTable::class',\n\t\t\t'Foos' => '\\TestApp\\Model\\Table\\FoosTable::class',\n\t\t\t'MyNamespace/MyPlugin.My' => '\\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class',\n\t\t\t'Relations.Bars' => '\\Relations\\Model\\Table\\BarsTable::class',\n\t\t\t'Relations.Foos' => '\\Relations\\Model\\Table\\FoosTable::class',\n\t\t\t'Relations.Users' => '\\Relations\\Model\\Table\\UsersTable::class',\n\t\t\t'SkipMe' => '\\TestApp\\Model\\Table\\SkipMeTable::class',\n\t\t\t'SkipSome' => '\\TestApp\\Model\\Table\\SkipSomeTable::class',\n\t\t\t'Wheels' => '\\TestApp\\Model\\Table\\WheelsTable::class',\n\t\t\t'WheelsExtra' => '\\TestApp\\Model\\Table\\WheelsExtraTable::class',\n\t\t];\n\t\t$this->assertSame($expectedMap, $map);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/PluginTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\Core\\Configure;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\PluginTask;\n\nclass PluginTaskTest extends TestCase {\n\n\tprotected PluginTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['Bake', 'Migrations', 'Shim']);\n\t\t$this->task = new PluginTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(1, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Core\\PluginApplicationInterface::addPlugin(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\t\t$map = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $map);\n\n\t\t$expected = [\n\t\t\t'Bake' => '\\Cake\\Http\\BaseApplication::class',\n\t\t\t'Cake/TwigView' => '\\Cake\\Http\\BaseApplication::class',\n\t\t\t'Migrations' => '\\Cake\\Http\\BaseApplication::class',\n\t\t\t'Shim' => '\\Cake\\Http\\BaseApplication::class',\n\t\t];\n\t\tif (version_compare(Configure::version(), '5.1.0', '<')) {\n\t\t\t$expected = [];\n\t\t}\n\t\t$this->assertSame($expected, $map);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/RequestTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse IdeHelper\\Generator\\Task\\RequestTask;\nuse Shim\\TestSuite\\TestCase;\n\nclass RequestTaskTest extends TestCase {\n\n\tprotected ?RequestTask $task = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new RequestTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(2, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Http\\ServerRequest::getParam()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'_ext' => \"'_ext'\",\n\t\t\t'_matchedRoute' => \"'_matchedRoute'\",\n\t\t\t'action' => \"'action'\",\n\t\t\t'controller' => \"'controller'\",\n\t\t\t'pass' => \"'pass'\",\n\t\t\t'plugin' => \"'plugin'\",\n\t\t\t'prefix' => \"'prefix'\",\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Http\\ServerRequest::getAttribute(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\n\t\t$expected = '\\Cake\\Http\\Session::class';\n\t\t$this->assertSame($expected, (string)$map['session']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/RoutePathTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\RoutePathTask;\n\nclass RoutePathTaskTest extends TestCase {\n\n\tprotected RoutePathTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['Awesome']);\n\t\t$this->task = new RoutePathTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(5, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\RegisterArgumentsSet $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame(RoutePathTask::SET_ROUTE_PATHS, $directive->toArray()['set']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($value) {\n\t\t\treturn (string)$value;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'Awesome.Admin/AwesomeHouses::openDoor' => \"'Awesome.Admin/AwesomeHouses::openDoor'\",\n\t\t\t'Bar::index' => \"'Bar::index'\",\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Routing\\Router::pathUrl()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($value) {\n\t\t\treturn (string)$value;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'argumentsSet(\\'routePaths\\')',\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\View\\Helper\\UrlHelper::buildFromPath()', $directive->toArray()['method']);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\View\\Helper\\HtmlHelper::linkFromPath()', $directive->toArray()['method']);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/TableAssociationTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\TableAssociationTask;\n\nclass TableAssociationTaskTest extends TestCase {\n\n\tprotected TableAssociationTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['IdeHelper', 'Shim', 'Awesome', 'Controllers', 'Relations', 'MyNamespace/MyPlugin']);\n\t\t$this->task = new TableAssociationTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(4, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\ORM\\Table::belongsTo(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\t\t$map = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $map);\n\n\t\t$expectedMap = [\n\t\t\t'Awesome.Houses' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Awesome.Windows' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'BarBars' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'BarBarsAbstract' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Callbacks' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Cars' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Controllers.Houses' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'CustomFinder' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Exceptions' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Foos' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'MyNamespace/MyPlugin.My' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Relations.Bars' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Relations.Foos' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Relations.Users' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'SkipMe' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'SkipSome' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'Wheels' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t\t'WheelsExtra' => '\\Cake\\ORM\\Association\\BelongsTo::class',\n\t\t];\n\t\t$this->assertSame($expectedMap, $map);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/TableFinderTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\Core\\Configure;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\Task\\TableFinderTask;\nuse Shim\\TestSuite\\TestTrait;\nuse TestApp\\Model\\Table\\CustomFinderTable;\n\nclass TableFinderTaskTest extends TestCase {\n\n\tuse TestTrait;\n\n\tprotected TableFinderTask $task;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\tConfigure::write('IdeHelper.preemptive', true);\n\n\t\t$this->task = new TableFinderTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tConfigure::delete('IdeHelper');\n\n\t\tparent::tearDown();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(3, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\ORM\\Table::find(0)', $directive->toArray()['method']);\n\n\t\t$map = $directive->toArray()['map'];\n\t\t$map = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $map);\n\n\t\t$expectedMap = [\n\t\t\t'all' => '\\Cake\\ORM\\Query\\SelectQuery::class',\n\t\t\t'children' => '\\Cake\\ORM\\Query\\SelectQuery::class',\n\t\t\t'list' => '\\Cake\\ORM\\Query\\SelectQuery::class',\n\t\t\t'path' => '\\Cake\\ORM\\Query\\SelectQuery::class',\n\t\t\t'somethingCustom' => '\\Cake\\ORM\\Query\\SelectQuery::class',\n\t\t\t'threaded' => '\\Cake\\ORM\\Query\\SelectQuery::class',\n\t\t\t'treeList' => '\\Cake\\ORM\\Query\\SelectQuery::class',\n\t\t];\n\t\t$this->assertSame($expectedMap, $map);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAddMethod() {\n\t\t$result = [];\n\n\t\t$class = CustomFinderTable::class;\n\t\t$method = 'findSomethingCustom';\n\n\t\t/** @uses \\IdeHelper\\Generator\\Task\\TableFinderTask::addMethod() */\n\t\t$result = $this->invokeMethod($this->task, 'addMethod', [$result, $method, $class]);\n\t\t$this->assertSame(['somethingCustom'], $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAddMethodInvalid() {\n\t\t$result = [];\n\n\t\t$class = CustomFinderTable::class;\n\n\t\t$method = 'findBySomethingCustom';\n\t\t/** @uses \\IdeHelper\\Generator\\Task\\TableFinderTask::addMethod() */\n\t\t$result = $this->invokeMethod($this->task, 'addMethod', [$result, $method, $class]);\n\t\t$this->assertSame([], $result);\n\n\t\t$method = 'findSomethingCustomBySomethingElse';\n\t\t/** @uses \\IdeHelper\\Generator\\Task\\TableFinderTask::addMethod() */\n\t\t$result = $this->invokeMethod($this->task, 'addMethod', [$result, $method, $class]);\n\t\t$this->assertSame([], $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/TranslationKeyTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse Cake\\Core\\Configure;\nuse IdeHelper\\Generator\\Task\\TranslationKeyTask;\nuse Shim\\TestSuite\\TestCase;\n\nclass TranslationKeyTaskTest extends TestCase {\n\n\tprotected ?TranslationKeyTask $task = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new TranslationKeyTask();\n\n\t\t$this->loadPlugins(['Awesome', 'Controllers', 'MyNamespace/MyPlugin', 'Relations', 'Shim', 'IdeHelper']);\n\t\tConfigure::write('App.paths.locales', [\n\t\t\tAPP_ROOT . DS . 'locales' . DS,\n\t\t]);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(3, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\__()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'A {0} placeholder' => \"'A {0} placeholder'\",\n\t\t\t'Some \\\\\\' special case' => '\\'Some \\\\\\' special case\\'',\n\t\t\t'my foo and bar' => \"'my foo and bar'\",\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\__d()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'A plugin translation' => '\\'A plugin translation\\'',\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\ExpectedArguments $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\__d()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t// 'my_plugin' is now superseded by 'my_namespace/my_plugin'\n\t\t$expected = [\n\t\t\t'awesome' => '\\'awesome\\'',\n\t\t\t'cake' => '\\'cake\\'',\n\t\t\t'controllers' => '\\'controllers\\'',\n\t\t\t'ide_helper' => '\\'ide_helper\\'',\n\t\t\t'my_namespace/my_plugin' => '\\'my_namespace/my_plugin\\'',\n\t\t\t'relations' => '\\'relations\\'',\n\t\t\t'shim' => '\\'shim\\'',\n\t\t];\n\t\tif (version_compare(Configure::version(), '5.1.0', '<')) {\n\t\t\t$expected = [\n\t\t\t\t'awesome' => '\\'awesome\\'',\n\t\t\t\t'bake' => '\\'bake\\'',\n\t\t\t\t'cake' => '\\'cake\\'',\n\t\t\t\t'cake/twig_view' => '\\'cake/twig_view\\'',\n\t\t\t\t'controllers' => '\\'controllers\\'',\n\t\t\t\t'ide_helper' => '\\'ide_helper\\'',\n\t\t\t\t'migrations' => '\\'migrations\\'',\n\t\t\t\t'my_namespace/my_plugin' => '\\'my_namespace/my_plugin\\'',\n\t\t\t\t'relations' => '\\'relations\\'',\n\t\t\t\t'shim' => '\\'shim\\'',\n\t\t\t];\n\t\t}\n\n\t\t$this->assertSame($expected, $list);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/Task/ValidationTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator\\Task;\n\nuse IdeHelper\\Generator\\Directive\\RegisterArgumentsSet;\nuse IdeHelper\\Generator\\Task\\ValidationTask;\nuse Shim\\TestSuite\\TestCase;\n\nclass ValidationTaskTest extends TestCase {\n\n\tprotected ?ValidationTask $task = null;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->task = new ValidationTask();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCollect() {\n\t\t$result = $this->task->collect();\n\n\t\t$this->assertCount(15, $result);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\RegisterArgumentsSet $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertInstanceOf(RegisterArgumentsSet::class, $directive);\n\t\t$this->assertSame(ValidationTask::SET_VALIDATION_WHEN, $directive->toArray()['set']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t\"'create'\",\n\t\t\t\"'update'\",\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\n\t\t/** @var \\IdeHelper\\Generator\\Directive\\Override $directive */\n\t\t$directive = array_shift($result);\n\t\t$this->assertSame('\\Cake\\Validation\\Validator::requirePresence()', $directive->toArray()['method']);\n\n\t\t$list = $directive->toArray()['list'];\n\t\t$list = array_map(function ($className) {\n\t\t\treturn (string)$className;\n\t\t}, $list);\n\n\t\t$expected = [\n\t\t\t'argumentsSet(\\'validationWhen\\')',\n\t\t];\n\t\t$this->assertSame($expected, $list);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Generator/TaskCollectionTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Generator;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Generator\\TaskCollection;\n\nclass TaskCollectionTest extends TestCase {\n\n\tprotected TaskCollection $taskCollection;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->taskCollection = new TaskCollection();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testTasks() {\n\t\t$result = $this->taskCollection->tasks();\n\n\t\t$this->assertNotEmpty($result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Illuminator/IlluminatorTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Illuminator;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\Illuminator\\Illuminator;\nuse IdeHelper\\Illuminator\\TaskCollection;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass IlluminatorTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\tprotected Illuminator $illuminator;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$consoleIo->level($consoleIo::VERBOSE);\n\t\t$this->io = new Io($consoleIo);\n\n\t\t$taskCollection = new TaskCollection($this->io, ['dry-run' => true]);\n\n\t\t$this->illuminator = new Illuminator($taskCollection);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIlluminate() {\n\t\t$path = TEST_FILES;\n\t\t$count = $this->illuminator->illuminate($path, null);\n\n\t\t$this->assertSame(14, $count);\n\n\t\t$out = $this->out->output();\n\n\t\t$this->assertTextContains('public const FIELD_ID = \\'id\\';', $out);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Illuminator/Task/ControllerDefaultTableTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Illuminator\\Task;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\Illuminator\\Task\\ControllerDefaultTableTask;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass ControllerDefaultTableTaskTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRun() {\n\t\t$task = $this->_getTask();\n\n\t\t$result = $task->shouldRun('src/Controller/NoTableController.php');\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('src/Controller/AppController.php');\n\t\t$this->assertFalse($result);\n\n\t\t$result = $task->shouldRun('src/Model/Table/Wheels.php');\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRunWithDifferentPathSeparators() {\n\t\t$task = $this->_getTask();\n\n\t\t// Forward slashes\n\t\t$result = $task->shouldRun('src/Controller/TestController.php');\n\t\t$this->assertTrue($result);\n\n\t\t// Backslashes (Windows-style)\n\t\t$result = $task->shouldRun('src\\\\Controller\\\\TestController.php');\n\t\t$this->assertTrue($result);\n\n\t\t// Should fail without Controller in path\n\t\t$result = $task->shouldRun('src/TestController.php');\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunWithExistingTable() {\n\t\t$task = $this->_getTask();\n\n\t\t$path = APP . 'Controller/FoosController.php';\n\t\t$this->assertFileExists($path);\n\n\t\t$content = file_get_contents($path);\n\t\t$result = $task->run($content, $path);\n\n\t\t// Should not add defaultTable if table exists\n\t\t$this->assertSame($content, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunWithoutTable() {\n\t\t$task = $this->_getTask();\n\n\t\t$content = <<<'PHP'\n<?php\nnamespace App\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass NoTableController extends Controller {\n}\n\nPHP;\n\n\t\t$expected = <<<'PHP'\n<?php\nnamespace App\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass NoTableController extends Controller {\n\n\tprotected ?string $defaultTable = '';\n}\n\nPHP;\n\n\t\t$path = 'src/Controller/NoTableController.php';\n\t\t$result = $task->run($content, $path);\n\n\t\t$this->assertTextEquals($expected, $result);\n\t\t$this->assertStringContainsString(\"protected ?string \\$defaultTable = '';\", $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunWithExistingProperty() {\n\t\t$task = $this->_getTask();\n\n\t\t$content = <<<'PHP'\n<?php\nnamespace App\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass CustomController extends Controller {\n\n\tprotected ?string $defaultTable = 'CustomTable';\n\n}\n\nPHP;\n\n\t\t$path = 'src/Controller/CustomController.php';\n\t\t$result = $task->run($content, $path);\n\n\t\t// Should not add if property already exists\n\t\t$this->assertSame($content, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunWithOtherProperties() {\n\t\t$task = $this->_getTask();\n\n\t\t$content = <<<'PHP'\n<?php\nnamespace App\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass PropertiesController extends Controller {\n\n\tprotected string $myProperty = 'test';\n\n\tpublic function index() {\n\t}\n}\n\nPHP;\n\n\t\t$expected = <<<'PHP'\n<?php\nnamespace App\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass PropertiesController extends Controller {\n\n\tprotected ?string $defaultTable = '';\n\n\tprotected string $myProperty = 'test';\n\n\tpublic function index() {\n\t}\n}\n\nPHP;\n\n\t\t$path = 'src/Controller/PropertiesController.php';\n\t\t$result = $task->run($content, $path);\n\n\t\t$this->assertTextEquals($expected, $result);\n\t\t$this->assertStringContainsString(\"protected ?string \\$defaultTable = '';\", $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunWithPluginController() {\n\t\t$task = $this->_getTask();\n\n\t\t$content = <<<'PHP'\n<?php\nnamespace MyPlugin\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass CustomController extends Controller {\n}\n\nPHP;\n\n\t\t$expected = <<<'PHP'\n<?php\nnamespace MyPlugin\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass CustomController extends Controller {\n\n\tprotected ?string $defaultTable = '';\n}\n\nPHP;\n\n\t\t$path = 'plugins/MyPlugin/src/Controller/CustomController.php';\n\t\t$result = $task->run($content, $path);\n\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunWithNestedPluginNamespace() {\n\t\t$task = $this->_getTask();\n\n\t\t$content = <<<'PHP'\n<?php\nnamespace Vendor\\MyPlugin\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass CustomController extends Controller {\n}\n\nPHP;\n\n\t\t$expected = <<<'PHP'\n<?php\nnamespace Vendor\\MyPlugin\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass CustomController extends Controller {\n\n\tprotected ?string $defaultTable = '';\n}\n\nPHP;\n\n\t\t$path = 'plugins/Vendor/MyPlugin/src/Controller/CustomController.php';\n\t\t$result = $task->run($content, $path);\n\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunWithFastPropertyCheck() {\n\t\t$task = $this->_getTask([\n\t\t\t'fastPropertyCheck' => true,\n\t\t]);\n\n\t\t$content = <<<'PHP'\n<?php\nnamespace App\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass ExistingController extends Controller {\n\n\tprotected ?string $defaultTable = '';\n}\n\nPHP;\n\n\t\t$path = 'src/Controller/ExistingController.php';\n\t\t$result = $task->run($content, $path);\n\n\t\t// Should not add if property already exists (using fast regex check)\n\t\t$this->assertSame($content, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunWithPropertyInCommentDefaultCheck() {\n\t\t$task = $this->_getTask();\n\n\t\t$content = <<<'PHP'\n<?php\nnamespace App\\Controller;\n\nuse Cake\\Controller\\Controller;\n\n/**\n * Controller with defaultTable in comment\n * protected ?string $defaultTable = '';\n */\nclass CommentController extends Controller {\n}\n\nPHP;\n\n\t\t$expected = <<<'PHP'\n<?php\nnamespace App\\Controller;\n\nuse Cake\\Controller\\Controller;\n\n/**\n * Controller with defaultTable in comment\n * protected ?string $defaultTable = '';\n */\nclass CommentController extends Controller {\n\n\tprotected ?string $defaultTable = '';\n}\n\nPHP;\n\n\t\t$path = 'src/Controller/CommentController.php';\n\t\t$result = $task->run($content, $path);\n\n\t\t// Token-based check correctly identifies no actual property exists\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunWithPropertyInCommentFastCheck() {\n\t\t$task = $this->_getTask([\n\t\t\t'fastPropertyCheck' => true,\n\t\t]);\n\n\t\t$content = <<<'PHP'\n<?php\nnamespace App\\Controller;\n\nuse Cake\\Controller\\Controller;\n\n/**\n * Controller with defaultTable in comment\n * protected ?string $defaultTable = '';\n */\nclass CommentController extends Controller {\n}\n\nPHP;\n\n\t\t$path = 'src/Controller/CommentController.php';\n\t\t$result = $task->run($content, $path);\n\n\t\t// Fast regex check has false-positive on comment (acceptable trade-off for speed)\n\t\t// If you need 100% accuracy, don't enable fastPropertyCheck\n\t\t$this->assertSame($content, $result);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Illuminator\\Task\\ControllerDefaultTableTask\n\t */\n\tprotected function _getTask(array $params = []) {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new ControllerDefaultTableTask($params);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Illuminator/Task/EntityFieldTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Illuminator\\Task;\n\nuse Cake\\Console\\ConsoleIo;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Console\\Io;\nuse IdeHelper\\Illuminator\\Task\\EntityFieldTask;\nuse Shim\\TestSuite\\ConsoleOutput;\n\nclass EntityFieldTaskTest extends TestCase {\n\n\tprotected ConsoleOutput $out;\n\n\tprotected ConsoleOutput $err;\n\n\tprotected Io $io;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->out = new ConsoleOutput();\n\t\t$this->err = new ConsoleOutput();\n\t\t$consoleIo = new ConsoleIo($this->out, $this->err);\n\t\t$this->io = new Io($consoleIo);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRun() {\n\t\t$task = $this->_getTask();\n\n\t\t$result = $task->shouldRun('src/Model/Entity/Wheel.php');\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('src/Model/Table/Wheels.php');\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIlluminate() {\n\t\t$task = $this->_getTask([\n\t\t\t'visibility' => false,\n\t\t]);\n\n\t\t$path = APP . 'Model/Entity/Complex/Wheel.php';\n\t\t$result = $task->run(file_get_contents($path), $path);\n\n\t\t$this->assertTextContains('const FIELD_ID = \\'id\\';', $result);\n\n\t\t$result = str_replace('    ', \"\\t\", $result);\n\t\t$expected = file_get_contents(TEST_FILES . 'Model/Entity/Constants/Wheel.php');\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIlluminateComplex() {\n\t\t$task = $this->_getTask([\n\t\t\t'visibility' => false,\n\t\t]);\n\n\t\t$path = APP . 'Model/Entity/Complex/Wheel.php';\n\t\t$this->assertFileExists($path);\n\t\t$result = $task->run(file_get_contents($path), $path);\n\n\t\t$result = str_replace('    ', \"\\t\", $result);\n\t\t$expected = file_get_contents(TEST_FILES . 'Model/Entity/Constants/Wheel.php');\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIlluminateComplex2() {\n\t\t$task = $this->_getTask([\n\t\t\t'visibility' => false,\n\t\t]);\n\n\t\t$path = APP . 'Model/Entity/Complex2/Wheel.php';\n\t\t$this->assertFileExists($path);\n\t\t$result = $task->run(file_get_contents($path), $path);\n\n\t\t$result = str_replace('    ', \"\\t\", $result);\n\t\t$expected = file_get_contents(TEST_FILES . 'Model/Entity/Constants/WheelComplex.php');\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIlluminateExisting() {\n\t\t$task = $this->_getTask([\n\t\t\t'visibility' => false,\n\t\t]);\n\n\t\t$path = TEST_FILES . 'Model/Entity/Constants/Wheel.php';\n\t\t$result = $task->run(file_get_contents($path), $path);\n\n\t\t$result = str_replace('    ', \"\\t\", $result);\n\t\t$expected = file_get_contents(TEST_FILES . 'Model/Entity/Constants/Wheel.php');\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIlluminateExistingPartial() {\n\t\t$task = $this->_getTask([\n\t\t\t'visibility' => false,\n\t\t]);\n\n\t\t$path = TEST_FILES . 'Model/Entity/ConstantsPartial/Wheel.php';\n\t\t$result = $task->run(file_get_contents($path), $path);\n\n\t\t$result = str_replace('    ', \"\\t\", $result);\n\t\t$expected = file_get_contents(TEST_FILES . 'Model/Entity/ConstantsPartialResult/Wheel.php');\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testIlluminateVisibility() {\n\t\t$task = $this->_getTask([\n\t\t\t'visibility' => true,\n\t\t]);\n\n\t\t$path = TEST_FILES . 'Model/Entity/Wheel.php';\n\t\t$result = $task->run(file_get_contents($path), $path);\n\n\t\t$this->assertTextContains('public const FIELD_ID = \\'id\\';', $result);\n\t}\n\n\t/**\n\t * @param array $params\n\t * @return \\IdeHelper\\Illuminator\\Task\\EntityFieldTask\n\t */\n\tprotected function _getTask(array $params = []) {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new EntityFieldTask($params);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Illuminator/Task/TableValidationLinkTaskTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Illuminator\\Task;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Annotator\\AbstractAnnotator;\nuse IdeHelper\\Illuminator\\Task\\TableValidationLinkTask;\n\nclass TableValidationLinkTaskTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testShouldRun(): void {\n\t\t$task = $this->getTask();\n\n\t\t$result = $task->shouldRun('src/Model/Table/WheelsTable.php');\n\t\t$this->assertTrue($result);\n\n\t\t$result = $task->shouldRun('src/Model/Table/Table.php');\n\t\t$this->assertFalse($result);\n\n\t\t$result = $task->shouldRun('src/Model/Entity/Wheel.php');\n\t\t$this->assertFalse($result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRun(): void {\n\t\t$task = $this->getTask();\n\n\t\t$path = TEST_FILES . 'Model/Table/Validation/IpRulesTable.php';\n\t\t$content = file_get_contents($path);\n\t\t$this->assertIsString($content);\n\n\t\t$result = $task->run($content, $path);\n\n\t\t$this->assertStringContainsString('/** @link verifyIpRanges() */', $result);\n\t\t$this->assertStringContainsString('/** @link verifyDenyRanges() */', $result);\n\n\t\t$result = str_replace('    ', \"\\t\", $result);\n\t\t$expected = file_get_contents(TEST_FILES . 'Model/Table/ValidationResult/IpRulesTable.php');\n\t\t$this->assertIsString($expected);\n\t\t$this->assertTextEquals($expected, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunNoChanges(): void {\n\t\t$task = $this->getTask();\n\n\t\t// Test with already annotated file\n\t\t$path = TEST_FILES . 'Model/Table/ValidationResult/IpRulesTable.php';\n\t\t$content = file_get_contents($path);\n\t\t$this->assertIsString($content);\n\n\t\t$result = $task->run($content, $path);\n\n\t\t// Should not add duplicate annotations\n\t\t$this->assertEquals($content, $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testRunNoTableProvider(): void {\n\t\t$task = $this->getTask();\n\n\t\t$content = <<<'PHP'\n<?php\nclass FooTable {\n\tpublic function validationDefault($validator) {\n\t\t$validator->add('email', 'valid', [\n\t\t\t'rule' => 'email',\n\t\t]);\n\t\treturn $validator;\n\t}\n}\nPHP;\n\n\t\t$result = $task->run($content, 'src/Model/Table/FooTable.php');\n\n\t\t// No @link should be added (no table provider)\n\t\t$this->assertStringNotContainsString('@link', $result);\n\t\t$this->assertEquals($content, $result);\n\t}\n\n\t/**\n\t * @param array<string, mixed> $params\n\t * @return \\IdeHelper\\Illuminator\\Task\\TableValidationLinkTask\n\t */\n\tprotected function getTask(array $params = []): TableValidationLinkTask {\n\t\t$params += [\n\t\t\tAbstractAnnotator::CONFIG_DRY_RUN => true,\n\t\t\tAbstractAnnotator::CONFIG_VERBOSE => true,\n\t\t];\n\n\t\treturn new TableValidationLinkTask($params);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Utility/AppPathTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Utility;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Utility\\AppPath;\n\nclass AppPathTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testGet() {\n\t\t$result = AppPath::get('View/Helper');\n\t\t$this->assertCount(1, $result);\n\n\t\t$path = array_shift($result);\n\t\t$this->assertTextContains('/tests/test_app/src/View/Helper/', $path);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Utility/AppTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Utility;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Utility\\App;\nuse IdeHelper\\View\\Helper\\DocBlockHelper;\n\nclass AppTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testClassName() {\n\t\t$result = App::className('Foos', 'Bar', 'Baz');\n\t\t$this->assertNull($result);\n\n\t\t$result = App::className('IdeHelper.DocBlock', 'View/Helper', 'Helper');\n\t\t$this->assertSame(DocBlockHelper::class, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Utility/GenericStringTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Utility;\n\nuse Cake\\Core\\Configure;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Utility\\GenericString;\n\nclass GenericStringTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testClassName() {\n\t\t$result = GenericString::generate('Foo');\n\t\t$this->assertSame('Foo[]', $result);\n\n\t\tConfigure::write('IdeHelper.arrayAsGenerics', true);\n\n\t\t$result = GenericString::generate('Foo');\n\n\t\tConfigure::delete('IdeHelper.arrayAsGenerics');\n\n\t\t$this->assertSame('array<Foo>', $result);\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testClassNameObject() {\n\t\t$result = GenericString::generate('\\Foo', '\\Bar');\n\t\t$this->assertSame('\\Foo[]|\\Bar', $result);\n\n\t\tConfigure::write('IdeHelper.objectAsGenerics', true);\n\n\t\t$result = GenericString::generate('\\Foo', '\\Bar');\n\n\t\tConfigure::delete('IdeHelper.objectAsGenerics');\n\n\t\t$this->assertSame('\\Bar<\\Foo>', $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Utility/PluginPathTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Utility;\n\nuse Cake\\Core\\Plugin;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Utility\\PluginPath;\n\nclass PluginPathTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testGet() {\n\t\t$plugin = 'Shim';\n\t\t$result = PluginPath::get($plugin);\n\t\t$this->assertTextContains('cakephp-ide-helper/vendor/dereuromark/cakephp-shim/', $result);\n\n\t\tPlugin::getCollection()->remove('Awesome');\n\n\t\t$plugin = 'Awesome';\n\t\t$result = PluginPath::get($plugin);\n\t\t$this->assertTextContains('cakephp-ide-helper/tests/test_app/plugins/Awesome/', $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Utility/PluginTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Utility;\n\nuse Cake\\Core\\Configure;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Utility\\Plugin;\n\nclass PluginTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->loadPlugins(['IdeHelper', 'Awesome', 'MyNamespace/MyPlugin']);\n\t\tConfigure::delete('IdeHelper.plugins');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tprotected function tearDown(): void {\n\t\tparent::tearDown();\n\n\t\tConfigure::delete('IdeHelper.plugins');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testAll() {\n\t\t$result = Plugin::all();\n\t\t$this->assertArrayHasKey('IdeHelper', $result);\n\t\t$this->assertArrayHasKey('Awesome', $result);\n\t\t$this->assertArrayHasKey('MyNamespace/MyPlugin', $result);\n\t\t$this->assertArrayNotHasKey('FooBar', $result);\n\n\t\tConfigure::write('IdeHelper.plugins', ['FooBar', '-MyNamespace/MyPlugin']);\n\n\t\t$result = Plugin::all();\n\t\t$this->assertArrayHasKey('FooBar', $result);\n\t\t$this->assertArrayNotHasKey('MyNamespace/MyPlugin', $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/Utility/TranslationParserTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\Utility;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\Utility\\TranslationParser;\n\nclass TranslationParserTest extends TestCase {\n\n\tprotected TranslationParser $translationParser;\n\n\t/**\n\t * @return void\n\t */\n\tprotected function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->translationParser = new TranslationParser();\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testParse() {\n\t\t$path = TEST_FILES . 'locales' . DS . 'default.po';\n\n\t\t$result = $this->translationParser->parse($path);\n\n\t\t$expected = [\n\t\t\t'A \"quoted\" string' => 'A \"quoted\" string',\n\t\t\t'A \"\"escape-quoted\"\" string' => 'A \"\"escape-quoted\"\" string',\n\t\t\t'A \\\\\\'literally quoted\\\\\\' string' => 'A \\\\\\'literally quoted\\\\\\' string',\n\t\t\t'A variable \\\\\\'\\\\\\'{0}\\\\\\'\\\\\\' be replaced.' => 'A variable \\\\\\'\\\\\\'{0}\\\\\\'\\\\\\' be replaced.',\n\t\t];\n\t\t$this->assertSame($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/ValueObject/ClassNameTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\ValueObject;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\ValueObject\\ClassName;\n\nclass ClassNameTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCreate(): void {\n\t\t$className = 'Foo\\\\Bar\\\\Baz';\n\t\t$result = ClassName::create($className);\n\t\t$this->assertSame($className, $result->raw());\n\n\t\t$result = ClassName::create('\\\\' . $className);\n\t\t$this->assertSame($className, $result->raw());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testToString(): void {\n\t\t$className = 'Foo\\\\Bar\\\\Baz';\n\t\t$object = ClassName::create($className);\n\n\t\t$result = (string)$object;\n\t\t$this->assertSame('\\\\' . $className . '::class', $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/ValueObject/DoubleQuoteStringNameTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\ValueObject;\n\nuse Cake\\TestSuite\\TestCase;\nuse TestApp\\ValueObject\\DoubleQuoteStringName;\n\nclass DoubleQuoteStringNameTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCreate(): void {\n\t\t$stringName = 'Foo.Baz';\n\t\t$result = DoubleQuoteStringName::create($stringName);\n\t\t$this->assertSame($stringName, $result->raw());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testToString(): void {\n\t\t$stringName = 'Foo.Baz';\n\t\t$object = DoubleQuoteStringName::create($stringName);\n\n\t\t$result = (string)$object;\n\t\t$this->assertSame('\"' . $stringName . '\"', $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/ValueObject/KeyValueTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\ValueObject;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\ValueObject\\ClassName;\nuse IdeHelper\\ValueObject\\KeyValue;\n\nclass KeyValueTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCreate(): void {\n\t\t$key = ClassName::create(KeyValue::class);\n\n\t\t$value = ClassName::create(KeyValue::class);\n\t\t$result = KeyValue::create($key, $value);\n\n\t\t$this->assertSame($key, $result->key());\n\t\t$this->assertSame($value, $result->value());\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/ValueObject/LiteralNameTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\ValueObject;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\ValueObject\\LiteralName;\n\nclass LiteralNameTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCreate(): void {\n\t\t$stringName = 'Foo.Baz';\n\t\t$result = LiteralName::create($stringName);\n\t\t$this->assertSame($stringName, $result->raw());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testToString(): void {\n\t\t$stringName = 'Foo.Baz';\n\t\t$object = LiteralName::create($stringName);\n\n\t\t$result = (string)$object;\n\t\t$this->assertSame($stringName, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/ValueObject/StringNameTest.php",
    "content": "<?php\n\nnamespace IdeHelper\\Test\\TestCase\\ValueObject;\n\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass StringNameTest extends TestCase {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testCreate(): void {\n\t\t$stringName = 'Foo.Baz';\n\t\t$result = StringName::create($stringName);\n\t\t$this->assertSame($stringName, $result->raw());\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function testToString(): void {\n\t\t$stringName = 'Foo.Baz';\n\t\t$object = StringName::create($stringName);\n\n\t\t$result = (string)$object;\n\t\t$this->assertSame('\\'' . $stringName . '\\'', $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/TestCase/View/Helper/DocBlockHelperTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace IdeHelper\\Test\\TestCase\\View\\Helper;\n\nuse Bake\\View\\BakeView;\nuse Cake\\Http\\Response;\nuse Cake\\Http\\ServerRequest as Request;\nuse Cake\\TestSuite\\TestCase;\nuse IdeHelper\\View\\Helper\\DocBlockHelper;\nuse PHPUnit\\Framework\\Attributes\\CoversClass;\n\n/**\n * DocBlockHelper Test\n */\n#[CoversClass(DocBlockHelper::class)]\nclass DocBlockHelperTest extends TestCase {\n\n\t/**\n\t * @var \\IdeHelper\\View\\Helper\\DocBlockHelper\n\t */\n\tprotected $DocBlockHelper;\n\n\t/**\n\t * setUp method\n\t *\n\t * @return void\n\t */\n\tpublic function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$request = new Request();\n\t\t$response = new Response();\n\t\t$View = new BakeView($request, $response);\n\t\t$this->DocBlockHelper = new DocBlockHelper($View);\n\t}\n\n\t/**\n\t * tearDown method\n\t *\n\t * @return void\n\t */\n\tpublic function tearDown(): void {\n\t\tparent::tearDown();\n\t\tunset($this->DocBlockHelper);\n\t}\n\n\t/**\n\t * Tests the classDescription method including annotation spacing\n\t *\n\t * @return void\n\t */\n\tpublic function testBuildTableAnnotations(): void {\n\t\t$associations = [];\n\t\t$associationInfo = [\n\t\t\t'BelongsTo' => [\n\t\t\t\t'User' => [\n\t\t\t\t\t'className' => 'Users',\n\t\t\t\t\t'foreignKey' => 'user_id',\n\t\t\t\t],\n\t\t\t],\n\t\t];\n\t\t$behaviors = [];\n\t\t$entity = 'Foo';\n\t\t$namespace = 'Bar';\n\n\t\t$result = $this->DocBlockHelper->buildTableAnnotations($associations, $associationInfo, $behaviors, $entity, $namespace);\n\n\t\t$expected = [\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo newEmptyEntity()',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo newEntity(array $data, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo[] newEntities(array $data, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo get(mixed $primaryKey, array|string $finder = \\'all\\', \\\\Psr\\\\SimpleCache\\\\CacheInterface|string|null $cache = null, \\\\Closure|string|null $cacheKey = null, mixed ...$args)',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo findOrCreate(\\\\Cake\\\\ORM\\\\Query\\\\SelectQuery|callable|array $search, ?callable $callback = null, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo patchEntity(\\\\Cake\\\\Datasource\\\\EntityInterface $entity, array $data, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo[] patchEntities(iterable $entities, array $data, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo|false save(\\\\Cake\\\\Datasource\\\\EntityInterface $entity, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo saveOrFail(\\\\Cake\\\\Datasource\\\\EntityInterface $entity, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo[]|\\\\Cake\\\\Datasource\\\\ResultSetInterface<\\\\Bar\\\\Model\\\\Entity\\\\Foo>|false saveMany(iterable $entities, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo[]|\\\\Cake\\\\Datasource\\\\ResultSetInterface<\\\\Bar\\\\Model\\\\Entity\\\\Foo> saveManyOrFail(iterable $entities, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo[]|\\\\Cake\\\\Datasource\\\\ResultSetInterface<\\\\Bar\\\\Model\\\\Entity\\\\Foo>|false deleteMany(iterable $entities, array $options = [])',\n\t\t\t'@method \\\\Bar\\\\Model\\\\Entity\\\\Foo[]|\\\\Cake\\\\Datasource\\\\ResultSetInterface<\\\\Bar\\\\Model\\\\Entity\\\\Foo> deleteManyOrFail(iterable $entities, array $options = [])',\n\t\t];\n\t\t$this->assertEquals($expected, $result);\n\t}\n\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php\n\nuse Cake\\Cache\\Cache;\nuse Cake\\Controller\\Controller;\nuse Cake\\Core\\Configure;\nuse Cake\\Database\\TypeFactory;\nuse Cake\\Datasource\\ConnectionManager;\nuse Cake\\TestSuite\\Fixture\\SchemaLoader;\n\nif (!defined('DS')) {\n\tdefine('DS', DIRECTORY_SEPARATOR);\n}\n\ndefine('PLUGIN_ROOT', dirname(__DIR__));\ndefine('APP_DIR', 'src');\n\n// Point app constants to the test app.\ndefine('APP_ROOT', PLUGIN_ROOT . DS . 'tests' . DS . 'test_app');\ndefine('ROOT', APP_ROOT);\ndefine('APP', APP_ROOT . DS . APP_DIR . DS);\ndefine('PLUGINS', APP_ROOT . DS . 'plugins' . DS);\ndefine('TEST_FILES', PLUGIN_ROOT . DS . 'tests' . DS . 'test_files' . DS);\n\ndefine('TMP', PLUGIN_ROOT . DS . 'tmp' . DS);\nif (!is_dir(TMP)) {\n\tmkdir(TMP, 0770, true);\n}\ndefine('CONFIG', PLUGIN_ROOT . DS . 'config' . DS);\n\ndefine('LOGS', TMP . 'logs' . DS);\ndefine('CACHE', TMP . 'cache' . DS);\n\ndefine('CAKE_CORE_INCLUDE_PATH', PLUGIN_ROOT . '/vendor/cakephp/cakephp');\ndefine('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS);\ndefine('CAKE', CORE_PATH . APP_DIR . DS);\n\nrequire dirname(__DIR__) . '/vendor/autoload.php';\nrequire CORE_PATH . 'config/bootstrap.php';\nrequire CAKE . 'functions.php';\n\nConfigure::write('App', [\n\t'namespace' => 'TestApp',\n\t'encoding' => 'utf-8',\n\t'paths' => [\n\t\t'templates' => [APP_ROOT . DS . 'templates' . DS],\n\t],\n]);\nConfigure::write('debug', true);\n\n$cache = [\n\t'default' => [\n\t\t'engine' => 'File',\n\t],\n\t'_cake_translations_' => [\n\t\t'className' => 'File',\n\t\t'prefix' => 'myapp_cake_translations_',\n\t\t'path' => CACHE . 'persistent/',\n\t\t'serialize' => true,\n\t\t'duration' => '+10 seconds',\n\t],\n\t'_cake_model_' => [\n\t\t'className' => 'File',\n\t\t'prefix' => 'myapp_cake_model_',\n\t\t'path' => CACHE . 'models/',\n\t\t'serialize' => 'File',\n\t\t'duration' => '+10 seconds',\n\t],\n];\n\nCache::setConfig($cache);\n\nTypeFactory::build('time');\nTypeFactory::build('date');\nTypeFactory::build('datetime');\nTypeFactory::build('timestamp');\n\nclass_alias(Controller::class, 'App\\Controller\\AppController');\n\n// Ensure default test connection is defined\nif (!getenv('DB_URL')) {\n\tputenv('DB_URL=sqlite:///:memory:');\n}\n\nConnectionManager::setConfig('test', [\n\t'url' => getenv('DB_URL'),\n\t'timezone' => 'UTC',\n\t'quoteIdentifiers' => true,\n\t'cacheMetadata' => true,\n]);\n\nif (env('FIXTURE_SCHEMA_METADATA')) {\n\t$loader = new SchemaLoader();\n\t$loader->loadInternalFile(env('FIXTURE_SCHEMA_METADATA'));\n}\n"
  },
  {
    "path": "tests/phpstan.neon",
    "content": "parameters:\n\tlevel: 6\n\tpaths:\n\t\t- TestCase/\n\tbootstrapFiles:\n\t\t- bootstrap.php\n\t\t- shim.php\n\tearlyTerminatingMethodCalls:\n\t\tCake\\Console\\BaseCommand:\n\t\t\t- abort\n\tignoreErrors:\n\t\t- identifier: missingType.iterableValue\n"
  },
  {
    "path": "tests/schema.php",
    "content": "<?php\n\nuse Cake\\Utility\\Inflector;\n\n$tables = [];\n\n/**\n * @var \\DirectoryIterator<\\DirectoryIterator> $iterator\n */\n$iterator = new DirectoryIterator(__DIR__ . DS . 'Fixture');\nforeach ($iterator as $file) {\n\tif (!preg_match('/(\\w+)Fixture.php$/', (string)$file, $matches)) {\n\t\tcontinue;\n\t}\n\n\t$name = $matches[1];\n\t$tableName = Inflector::underscore($name);\n\t$class = 'IdeHelper\\\\Test\\\\Fixture\\\\' . $name . 'Fixture';\n\ttry {\n\t\t$object = (new ReflectionClass($class))->getProperty('fields');\n\t} catch (ReflectionException $e) {\n\t\tcontinue;\n\t}\n\n\t$array = $object->getDefaultValue();\n\t$constraints = $array['_constraints'] ?? [];\n\t$indexes = $array['_indexes'] ?? [];\n\tunset($array['_constraints'], $array['_indexes'], $array['_options']);\n\t$table = [\n\t\t'table' => $tableName,\n\t\t'columns' => $array,\n\t\t'constraints' => $constraints,\n\t\t'indexes' => $indexes,\n\t];\n\t$tables[$tableName] = $table;\n}\n\nreturn $tables;\n"
  },
  {
    "path": "tests/shim.php",
    "content": "<?php\n\nuse PHP_CodeSniffer\\Config;\nuse PHPUnit\\Framework\\TestCase;\n\n$manualAutoload = getcwd() . '/vendor/squizlabs/php_codesniffer/autoload.php';\nif (!class_exists(Config::class) && file_exists($manualAutoload)) {\n\trequire $manualAutoload;\n}\n\nerror_reporting(E_ALL & ~E_USER_DEPRECATED);\n\nif (!defined('T_NULLABLE')) {\n\tdefine('T_NULLABLE', 'PHPCS_T_NULLABLE');\n}\nif (!defined('T_DOC_COMMENT_TAG')) {\n\tdefine('T_DOC_COMMENT_TAG', 'PHPCS_T_DOC_COMMENT_TAG');\n}\nif (!defined('T_SEMICOLON')) {\n\tdefine('T_SEMICOLON', 'PHPCS_T_SEMICOLON');\n}\n\nif (!class_exists(TestCase::class)) {\n\trequire 'TestCase.php';\n}\n"
  },
  {
    "path": "tests/test_app/locales/de/default.po",
    "content": "\nmsgid \"my foo and bar\"\nmsgstr \"mein foo und bar\"\n\nmsgid \"A {0} placeholder\"\nmsgstr \"Ein {0} Platzhalter\"\n"
  },
  {
    "path": "tests/test_app/locales/de/my_plugin.po",
    "content": "\nmsgid \"A plugin translation\"\nmsgstr \"Eine Plugin Übersetzung\"\n"
  },
  {
    "path": "tests/test_app/locales/en/default.po",
    "content": "\nmsgid \"my foo and bar\"\nmsgstr \"\"\n\nmsgid \"Some ' special case\"\nmsgstr \"Ein ' besonderer Fall\"\n"
  },
  {
    "path": "tests/test_app/plugins/Awesome/src/AwesomePlugin.php",
    "content": "<?php\nnamespace Awesome;\n\nuse Cake\\Core\\BasePlugin;\n\nclass AwesomePlugin extends BasePlugin {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Awesome/src/Controller/Admin/AwesomeHousesController.php",
    "content": "<?php\nnamespace Awesome\\Controller\\Admin;\n\nuse Cake\\Controller\\Controller;\n\nclass AwesomeHousesController extends Controller {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function openDoor() {\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Awesome/src/Model/Table/HousesTable.php",
    "content": "<?php\nnamespace Awesome\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\HasMany<\\Awesome\\Model\\Table\\WindowsTable> $Windows\n */\nclass HousesTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->hasMany('Awesome.Windows');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Awesome/src/Model/Table/WindowsTable.php",
    "content": "<?php\nnamespace Awesome\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\Awesome\\Model\\Table\\HousesTable> $Houses\n */\nclass WindowsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Awesome.Houses');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Awesome/templates/element/pagination.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n */\n?>\n<p>Pagination</p>\n"
  },
  {
    "path": "tests/test_app/plugins/Controllers/src/Controller/GenericController.php",
    "content": "<?php\nnamespace Controllers\\Controller;\n\nuse Cake\\Controller\\Controller;\n\n#[\\AllowDynamicProperties]\nclass GenericController extends Controller {\n\n\tprotected ?string $defaultTable = '';\n\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Controllers/src/Controller/HousesController.php",
    "content": "<?php\nnamespace Controllers\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass HousesController extends Controller {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Controllers/src/Controller/WindowsController.php",
    "content": "<?php\nnamespace Controllers\\Controller;\n\nuse Cake\\Controller\\Controller;\n\nclass WindowsController extends Controller {\n\n\tprotected ?string $defaultTable = 'Awesome.Windows';\n\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Controllers/src/ControllersPlugin.php",
    "content": "<?php\nnamespace Controllers;\n\nuse Cake\\Core\\BasePlugin;\n\nclass ControllersPlugin extends BasePlugin {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Controllers/src/Model/Table/HousesTable.php",
    "content": "<?php\nnamespace Controllers\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\nclass HousesTable extends Table {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/MyNamespace/MyPlugin/src/Controller/Component/MyComponent.php",
    "content": "<?php\nnamespace MyNamespace\\MyPlugin\\Controller\\Component;\n\nuse Cake\\Controller\\Component;\n\nclass MyComponent extends Component {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/MyNamespace/MyPlugin/src/Model/Behavior/MyBehavior.php",
    "content": "<?php\nnamespace MyNamespace\\MyPlugin\\Model\\Behavior;\n\nuse Cake\\ORM\\Behavior;\n\nclass MyBehavior extends Behavior {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/MyNamespace/MyPlugin/src/Model/Table/MyTable.php",
    "content": "<?php\n\nnamespace MyNamespace\\MyPlugin\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\nclass MyTable extends Table {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/MyNamespace/MyPlugin/src/MyPluginPlugin.php",
    "content": "<?php\nnamespace MyNamespace\\MyPlugin;\n\nuse Cake\\Core\\BasePlugin;\n\nclass MyPluginPlugin extends BasePlugin {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/MyNamespace/MyPlugin/tests/Fixture/Sub/MyFixture.php",
    "content": "<?php\n\nnamespace MyNamespace\\MyPlugin\\Test\\TestCase\\Fixture\\Sub;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass MyFixture extends TestFixture {\n\n\tpublic array $fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],\n\t];\n\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Relations/src/Model/Entity/Bar.php",
    "content": "<?php\nnamespace Relations\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\nclass Bar extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Relations/src/Model/Entity/Foo.php",
    "content": "<?php\nnamespace Relations\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\nclass Foo extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Relations/src/Model/Entity/User.php",
    "content": "<?php\nnamespace Relations\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\nclass User extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Relations/src/Model/Table/BarsTable.php",
    "content": "<?php\nnamespace Relations\\Model\\Table;\n\nuse Cake\\Database\\Schema\\TableSchema;\nuse Cake\\Database\\Schema\\TableSchemaInterface;\nuse Cake\\ORM\\Table;\n\nclass BarsTable extends Table {\n\n\tprotected array $_fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'autoIncrement' => true],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null],\n\t\t'user_id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => true, 'default' => null],\n\t];\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t// Nullable relation\n\t\t$this->belongsTo('Relations.Users');\n\t}\n\n\t/**\n\t * @return \\Cake\\Database\\Schema\\TableSchemaInterface\n\t */\n\tpublic function getSchema(): TableSchemaInterface {\n\t\t$tableSchema = new TableSchema($this->getTable());\n\n\t\tforeach ($this->_fields as $field => $attributes) {\n\t\t\t$tableSchema->addColumn($field, $attributes);\n\t\t}\n\n\t\treturn $tableSchema;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Relations/src/Model/Table/FoosTable.php",
    "content": "<?php\nnamespace Relations\\Model\\Table;\n\nuse Cake\\Database\\Schema\\TableSchema;\nuse Cake\\Database\\Schema\\TableSchemaInterface;\nuse Cake\\ORM\\Table;\n\nclass FoosTable extends Table {\n\n\tprotected array $_fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'autoIncrement' => true],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null],\n\t\t'user_id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null],\n\t\t'params' => ['type' => 'json', 'length' => null, 'null' => true, 'default' => null, 'comment' => '', 'precision' => null],\n\t];\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t// Required relation\n\t\t$this->belongsTo('Relations.Users');\n\t}\n\n\t/**\n\t * @return \\Cake\\Database\\Schema\\TableSchemaInterface\n\t */\n\tpublic function getSchema(): TableSchemaInterface {\n\t\t$tableSchema = new TableSchema($this->getTable());\n\n\t\tforeach ($this->_fields as $field => $attributes) {\n\t\t\t$tableSchema->addColumn($field, $attributes);\n\t\t}\n\n\t\treturn $tableSchema;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Relations/src/Model/Table/UsersTable.php",
    "content": "<?php\nnamespace Relations\\Model\\Table;\n\nuse Cake\\Database\\Schema\\TableSchema;\nuse Cake\\Database\\Schema\\TableSchemaInterface;\nuse Cake\\ORM\\Table;\n\nclass UsersTable extends Table {\n\n\tprotected array $_fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'autoIncrement' => true],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null],\n\t];\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->hasOne('Relations.Foos');\n\t\t$this->hasOne('Relations.Bars');\n\t}\n\n\t/**\n\t * @return \\Cake\\Database\\Schema\\TableSchemaInterface\n\t */\n\tpublic function getSchema(): TableSchemaInterface {\n\t\t$tableSchema = new TableSchema($this->getTable());\n\n\t\tforeach ($this->_fields as $field => $attributes) {\n\t\t\t$tableSchema->addColumn($field, $attributes);\n\t\t}\n\n\t\treturn $tableSchema;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/plugins/Relations/src/RelationsPlugin.php",
    "content": "<?php\nnamespace Relations;\n\nuse Cake\\Core\\BasePlugin;\n\nclass RelationsPlugin extends BasePlugin {\n}\n"
  },
  {
    "path": "tests/test_app/src/Application.php",
    "content": "<?php\n\nnamespace TestApp;\n\nuse Cake\\Http\\BaseApplication;\nuse Cake\\Http\\MiddlewareQueue;\nuse Cake\\Routing\\RouteBuilder;\n\nclass Application extends BaseApplication\n{\n\tpublic function bootstrap(): void\n\t{\n\t}\n\n\tpublic function routes(RouteBuilder $routes): void\n\t{\n\t}\n\n\tpublic function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue\n\t{\n\t\treturn $middlewareQueue;\n\t}\n}\n"
  },
  {
    "path": "tests/test_app/src/Command/MyCommand.php",
    "content": "<?php\nnamespace TestApp\\Command;\n\nuse Shim\\Command\\Command;\n\nclass MyCommand extends Command {\n\n\tprotected ?string $defaultTable = 'Cars';\n\n\t/** @var \\Relations\\Model\\Table\\BarsTable */\n\tprotected $Bars;\n\n\t/**\n\t * @return void\n\t */\n\tpublic function main() {\n\t\t$this->Wheels = $this->fetchTable('Wheels');\n\t\t$this->Bars = $this->fetchTable('Relations.Bars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Controller/AppController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\nuse Cake\\Controller\\Controller;\n\n/**\n * @property \\TestApp\\Controller\\Component\\MyOtherComponent $MyOther\n */\nclass AppController extends Controller {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function initialize(): void {\n\t\tparent::initialize();\n\n\t\t$this->loadComponent('Flash');\n\t\t$this->loadComponent('CheckHttpCache');\n\t\t$this->loadComponent('MyOther');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Controller/BarController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\nuse TestApp\\Model\\Table\\WheelsTable;\n\nclass BarController extends AppController {\n\n\tprotected ?string $defaultTable = 'BarBars';\n\n\tprotected WheelsTable $Wheels;\n\n\t/**\n\t * @return void\n\t */\n\tpublic function initialize(): void {\n\t\tparent::initialize();\n\n\t\t$this->loadComponent('Flash');\n\t\t$this->loadComponent('MyNamespace/MyPlugin.My');\n\n\t\t$this->Wheels = $this->fetchTable('Wheels');\n\t}\n\n\t/**\n\t * @return \\Cake\\Http\\Response|null|void\n\t */\n\tpublic function index() {\n\t\t$this->paginate($this->BarBars);\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Controller/Component/CheckHttpCacheComponent.php",
    "content": "<?php\nnamespace TestApp\\Controller\\Component;\n\nuse Cake\\Controller\\Component\\CheckHttpCacheComponent as CoreCheckHttpCacheComponent;\n\nclass CheckHttpCacheComponent extends CoreCheckHttpCacheComponent {\n}\n"
  },
  {
    "path": "tests/test_app/src/Controller/Component/MyComponent.php",
    "content": "<?php\nnamespace TestApp\\Controller\\Component;\n\nuse Cake\\Controller\\Component;\n\nclass MyComponent extends Component {\n\n\tpublic array $components = ['Flash', 'CheckHttpCache', 'MyNamespace/MyPlugin.My'];\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Controller/Component/MyControllerComponent.php",
    "content": "<?php\nnamespace TestApp\\Controller\\Component;\n\nuse Cake\\Controller\\Component;\nuse Cake\\Event\\EventInterface;\n\nclass MyControllerComponent extends Component {\n\n\t/**\n\t * @param \\Cake\\Event\\EventInterface $event\n\t *\n\t * @return void\n\t */\n\tpublic function beforeFilter(EventInterface $event): void {\n\t\t$user = (bool)$this->getController()->AuthUser->user();\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Controller/Component/MyOtherComponent.php",
    "content": "<?php\nnamespace TestApp\\Controller\\Component;\n\nuse Cake\\Controller\\Component;\n\n/**\n * @property \\TestApp\\Controller\\Component\\CheckHttpCacheComponent $CheckHttpCache\n * @property \\Cake\\Controller\\Component\\FormProtectionComponent $FormProtection\n */\nclass MyOtherComponent extends Component {\n\n\tpublic array $components = ['Flash', 'CheckHttpCache', 'SomeInvalidOneWillBeIgnored', 'FormProtection'];\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Controller/DynamicPropertiesController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\n#[\\AllowDynamicProperties]\nclass DynamicPropertiesController extends AppController {\n\n\tprotected ?string $defaultTable = 'BarBars';\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Controller/DynamicPropertiesExistingDocblockController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\n/**\n * Existing docblock\n */\n#[\\AllowDynamicProperties]\nclass DynamicPropertiesExistingDocblockController extends AppController {\n\n\tprotected ?string $defaultTable = 'BarBars';\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Controller/FoosController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\nclass FoosController extends AppController {\n}\n"
  },
  {
    "path": "tests/test_app/src/Custom/CustomClass.php",
    "content": "<?php\nnamespace TestApp\\Custom;\n\nuse Cake\\Datasource\\ModelAwareTrait;\n\n/**\n * @property \\TestApp\\Model\\Table\\BarBarsTable $BarBars\n */\nclass CustomClass {\n\n\tuse ModelAwareTrait;\n\n\t/**\n\t * @return void\n\t */\n\tpublic function initialize() {\n\t\t$this->fetchModel('Foos');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Custom/Nested/NestedClass.php",
    "content": "<?php\nnamespace TestApp\\Custom\\Nested;\n\nuse Cake\\Datasource\\ModelAwareTrait;\n\nclass NestedClass {\n\n\tuse ModelAwareTrait;\n\n\t/**\n\t * @return void\n\t */\n\tpublic function initialize() {\n\t\t$this->fetchModel('Foos');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Database/Type/UuidType.php",
    "content": "<?php\n/**\n * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)\n * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)\n *\n * Licensed under The MIT License\n * For full copyright and license information, please see the LICENSE.txt\n * Redistributions of files must retain the above copyright notice.\n *\n * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)\n * @link          https://cakephp.org CakePHP(tm) Project\n * @since         3.0.0\n * @license       https://opensource.org/licenses/mit-license.php MIT License\n */\nnamespace TestApp\\Database\\Type;\n\nuse Cake\\Database\\Type\\UuidType as CoreUuidType;\n\n/**\n * Provides behavior for the UUID type\n */\nclass UuidType extends CoreUuidType {\n}\n"
  },
  {
    "path": "tests/test_app/src/Generator/Task/TestDatabaseTableColumnTypeTask.php",
    "content": "<?php\n\nnamespace TestApp\\Generator\\Task;\n\nuse IdeHelper\\Generator\\Task\\DatabaseTableColumnTypeTask;\n\nclass TestDatabaseTableColumnTypeTask extends DatabaseTableColumnTypeTask\n{\n\t/**\n\t * @param string $name\n\t *\n\t * @return \\Phinx\\Db\\Adapter\\AdapterInterface|\\Migrations\\Db\\Adapter\\AdapterInterface\n\t */\n\tprotected function getAdapter(string $name = 'test') {\n\t\treturn parent::getAdapter($name);\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Generator/Task/TestEnvTask.php",
    "content": "<?php\n\nnamespace TestApp\\Generator\\Task;\n\nuse IdeHelper\\Generator\\Directive\\ExpectedArguments;\nuse IdeHelper\\Generator\\Task\\EnvTask;\nuse IdeHelper\\ValueObject\\StringName;\n\nclass TestEnvTask extends EnvTask {\n\n\t/**\n\t * @return \\IdeHelper\\ValueObject\\StringName[]\n\t */\n\tprotected function envKeys(): array {\n\t\t$list = parent::envKeys();\n\n\t\t$list = [\n\t\t\t'HTTP_HOST' => $list['HTTP_HOST'],\n\t\t];\n\n\t\treturn $list;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Generator/Task/TestFixtureTask.php",
    "content": "<?php\n\nnamespace TestApp\\Generator\\Task;\n\nuse IdeHelper\\Generator\\Task\\FixtureTask;\n\nclass TestFixtureTask extends FixtureTask {\n\n\t/**\n\t * @return \\IdeHelper\\ValueObject\\StringName[]\n\t */\n\tprotected function getFixtures(): array {\n\t\t$list = parent::getFixtures();\n\n\t\t$list = [\n\t\t\t'app.SmallWindows' => $list['app.SmallWindows'],\n\t\t\t'core.Posts' => $list['core.Posts'],\n\t\t\t'plugin.IdeHelper.Cars' => $list['plugin.IdeHelper.Cars'],\n\t\t\t'plugin.MyNamespace/MyPlugin.Sub/My' => $list['plugin.MyNamespace/MyPlugin.Sub/My'],\n\t\t];\n\n\t\treturn $list;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Mailer/UserMailer.php",
    "content": "<?php\n\nnamespace TestApp\\Mailer;\n\nuse Cake\\Mailer\\Mailer;\n\nclass UserMailer extends Mailer {\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/BarBar.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\nclass BarBar extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/BarBarsAbstract.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\nclass BarBarsAbstract extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/Callback.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\nclass Callback extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/Car.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * My car entity which is very cool.\n */\nclass Car extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/Complex/Wheel.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array{user?: int, account?: int|string, newContacts?: array<mixed>}|null $params !\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\Date $offer_date\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n *\n * @property-read string|null $virtual_one\n */\nclass Wheel extends Entity {\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/Complex2/Wheel.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array{utm_tags: array<mixed>|null}|null $params !\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\Date $offer_date\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n *\n * @property-read string|null $virtual_one\n */\nclass Wheel extends Entity {\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/Foo.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array|null $params\n * @property int $id\n * @property \\Cake\\I18n\\OldTime $created\n */\nclass Foo extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/PHP/ComplexType.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity\\PHP;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property array{foo: array<mixed>|null}|null $params !\n * @property \\Cake\\I18n\\Date $offer_date\n */\nclass ComplexType extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/PHP/Duplicates.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity\\PHP;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property \\Cake\\I18n\\Date $offer_date\n * @property array|null $params\n * @property int $content\n */\nclass Duplicates extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/PHP/Generics.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity\\PHP;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array|null $params\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n */\nclass Generics extends Entity {\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/PHP7/Virtual.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity\\PHP7;\n\nuse Cake\\ORM\\Entity;\n\nclass Virtual extends Entity {\n\n\tprotected function _getVirtualOne(): ?string {\n\t\treturn 'Virtual One';\n\t}\n\n\tprotected function _getVirtualTwo(): string {\n\t\treturn 'Virtual Two';\n\t}\n\n\t/**\n\t * @return array<int, array<string>>\n\t */\n\tprotected function _getNestedGeneric(): array {\n\t\treturn [[1 => ['a', 'b']]];\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/Virtual.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property-read bool $virtual_read_only This should be kept.\n */\nclass Virtual extends Entity {\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n\tprotected function _getVirtualTwo() {\n\t\t// Missing return type and docblock means mixed as result\n\t\treturn 'Virtual Two';\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tprotected function _getVirtualReadOnly() {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\TestApp\\Model\\Entity\\Wheel[] $wheels\n\t *\n\t * @return \\TestApp\\Model\\Entity\\Wheel[]\n\t */\n\tprotected function _getWheels($wheels = []) {\n\t\treturn $wheels;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Entity/Wheel.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\nclass Wheel extends Entity {\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Enum/CarStatus.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace TestApp\\Model\\Enum;\n\nenum CarStatus: int\n{\n\tcase NEW = 0;\n\tcase USED = 1;\n\n\tpublic function label(): string {\n\t\treturn match($this) {\n\t\t\tself::NEW => 'new',\n\t\t\tself::USED => 'used',\n\t\t};\n\t}\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/AbstractTable.php",
    "content": "<?php\n\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * Class AbstractTable\n */\nabstract class AbstractTable extends Table\n{\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void\n\t{\n\t\tparent::initialize($config);\n\n\t\t$this->addBehavior('Timestamp');\n\t}\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/BarBarsAbstractTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nclass BarBarsAbstractTable extends AbstractTable {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('bar_bars');\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('MyMy', [\n\t\t\t'className' => 'MyNamespace/MyPlugin.My',\n\t\t]);\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/BarBarsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\nclass BarBarsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/CallbacksTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse ArrayObject;\nuse Cake\\Datasource\\EntityInterface;\nuse Cake\\Event\\Event;\nuse Cake\\Event\\EventInterface;\nuse Cake\\ORM\\Table;\n\nclass CallbacksTable extends Table {\n\n\t/**\n\t * @param \\Cake\\Event\\Event $event Event\n\t * @param \\Cake\\Datasource\\EntityInterface $entity Entity\n\t * @param \\ArrayObject $options Options\n\t * @return void\n\t */\n\tpublic function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n\t/**\n\t * @param \\Cake\\Event\\EventInterface $event\n\t * @param \\Cake\\Datasource\\EntityInterface $entity\n\t * @param \\ArrayObject $options\n\t * @return void\n\t */\n\tpublic function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n\tpublic function beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/CarsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\Database\\Type\\EnumType;\nuse Cake\\ORM\\Table;\nuse TestApp\\Model\\Enum\\CarStatus;\n\nclass CarsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->hasMany('Wheels');\n\n\t\t$this->getSchema()->setColumnType('status', EnumType::from(CarStatus::class));\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/CustomFinderTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Query\\SelectQuery;\nuse Cake\\ORM\\Table;\n\nclass CustomFinderTable extends Table {\n\n\t/**\n\t * @param \\Cake\\ORM\\Query $query\n\t *\n\t * @return \\Cake\\ORM\\Query\n\t */\n\tpublic function findSomethingCustom(SelectQuery $query): SelectQuery {\n\t\treturn $query;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/ExceptionsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\nclass ExceptionsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('invalid');\n\n\t\t$this->belongsTo('TotallyInvalid');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/FoosTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\nclass FoosTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/SkipMeTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @inheritDoc\n */\nclass SkipMeTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/SkipSomeTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $Cars\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $CarsAwesome\n */\nclass SkipSomeTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function foo() {\n\t\t$this->CarsAwesome->doSthAwesome();\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/AbstractTable.php",
    "content": "<?php\n\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * Class AbstractTable\n */\nabstract class AbstractTable extends Table\n{\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void\n\t{\n\t\tparent::initialize($config);\n\n\t\t$this->addBehavior('Timestamp');\n\t}\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/BarBarsAbstractTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nclass BarBarsAbstractTable extends AbstractTable {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('bar_bars');\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('MyMy', [\n\t\t\t'className' => 'MyNamespace/MyPlugin.My',\n\t\t]);\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/BarBarsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\nclass BarBarsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/CallbacksTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse ArrayObject;\nuse Cake\\Datasource\\EntityInterface;\nuse Cake\\Event\\Event;\nuse Cake\\Event\\EventInterface;\nuse Cake\\ORM\\Table;\n\nclass CallbacksTable extends Table {\n\n\t/**\n\t * @param \\Cake\\Event\\Event $event Event\n\t * @param \\Cake\\Datasource\\EntityInterface $entity Entity\n\t * @param \\ArrayObject $options Options\n\t * @return void\n\t */\n\tpublic function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n\t/**\n\t * @param \\Cake\\Event\\EventInterface $event\n\t * @param \\Cake\\Datasource\\EntityInterface $entity\n\t * @param \\ArrayObject $options\n\t * @return void\n\t */\n\tpublic function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n\tpublic function beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/CarsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\Database\\Type\\EnumType;\nuse Cake\\ORM\\Table;\nuse TestApp\\Model\\Enum\\CarStatus;\n\nclass CarsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->hasMany('Wheels');\n\n\t\t$this->getSchema()->setColumnType('status', EnumType::from(CarStatus::class));\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/CustomFinderTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Query\\SelectQuery;\nuse Cake\\ORM\\Table;\n\nclass CustomFinderTable extends Table {\n\n\t/**\n\t * @param \\Cake\\ORM\\Query\\SelectQuery $query\n\t *\n\t * @return \\Cake\\ORM\\Query\\SelectQuery\n\t */\n\tpublic function findSomethingCustom(SelectQuery $query): SelectQuery {\n\t\treturn $query;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/ExceptionsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\nclass ExceptionsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('invalid');\n\n\t\t$this->belongsTo('TotallyInvalid');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/FoosTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\nclass FoosTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/SkipMeTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @inheritDoc\n */\nclass SkipMeTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/SkipSomeTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $Cars\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $CarsAwesome\n */\nclass SkipSomeTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function foo() {\n\t\t$this->CarsAwesome->doSthAwesome();\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/WheelsExtraTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsOldTable> $Cars\n */\nclass WheelsExtraTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('wheels');\n\t\t$this->belongsTo('Cars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/Specific/WheelsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @method \\TestApp\\Model\\Entity\\Wheeeeeeeel newEntity($data = null, array $options = [])\n */\nclass WheelsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\n\t\t$this->addBehavior('Tree');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/WheelsExtraTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsOldTable> $Cars\n */\nclass WheelsExtraTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('wheels');\n\t\t$this->belongsTo('Cars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/Model/Table/WheelsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @method \\TestApp\\Model\\Entity\\Wheeeeeeeel newEntity($data = null, array $options = [])\n */\nclass WheelsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\n\t\t$this->addBehavior('Tree');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/ValueObject/DoubleQuoteStringName.php",
    "content": "<?php\n\nnamespace TestApp\\ValueObject;\n\nuse IdeHelper\\ValueObject\\StringName;\n\n/**\n * Let's use \" instead of ' here.\n */\nclass DoubleQuoteStringName extends StringName {\n\n\t/**\n\t * @return string\n\t */\n\tpublic function __toString(): string {\n\t\treturn '\"' . $this->value . '\"';\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/View/AppView.php",
    "content": "<?php\nnamespace TestApp\\View;\n\nuse Cake\\View\\View;\n\n/**\n * @link https://book.cakephp.org/5/en/views.html#the-app-view\n */\nclass AppView extends View {\n}\n"
  },
  {
    "path": "tests/test_app/src/View/Cell/TestCell.php",
    "content": "<?php\ndeclare( strict_types = 1 );\n\nnamespace TestApp\\View\\Cell;\n\nuse Cake\\View\\Cell;\n\nclass TestCell extends Cell {\n\n\t/**\n\t * Default display method.\n\t *\n\t * @return void\n\t */\n\tpublic function display() {\n\n\t}\n\n\t/**\n\t * Custom display method.\n\t *\n\t * @return void\n\t */\n\tpublic function custom() {\n\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_app/src/View/CustomView.php",
    "content": "<?php\n\nnamespace TestApp\\View;\n\n/**\n * Custom View class for testing view annotation preservation.\n */\nclass CustomView extends AppView {\n}\n"
  },
  {
    "path": "tests/test_app/src/View/Helper/HtmlHelper.php",
    "content": "<?php\nnamespace TestApp\\View\\Helper;\n\nuse Cake\\View\\Helper\\HtmlHelper as CoreHtmlHelper;\n\nclass HtmlHelper extends CoreHtmlHelper {\n}\n"
  },
  {
    "path": "tests/test_app/src/View/Helper/MyHelper.php",
    "content": "<?php\nnamespace TestApp\\View\\Helper;\n\nuse Cake\\View\\Helper;\n\nclass MyHelper extends Helper {\n\n\tprotected array $helpers = [\n\t\t'Html',\n\t\t'Form',\n\t\t'Shim.Configure',\n\t\t'SomeInvalidOneWillBeIgnored',\n\t];\n\n}\n"
  },
  {
    "path": "tests/test_app/src/View/Helper/MyMethodHelper.php",
    "content": "<?php\nnamespace TestApp\\View\\Helper;\n\nuse Cake\\View\\Helper;\n\n/**\n * @property \\Cake\\View\\Helper\\FormHelper $Form\n * @method bool isAdmin()\n * @method bool isStaff()\n */\nclass MyMethodHelper extends Helper {\n\n\tprotected array $helpers = [\n\t\t'Html',\n\t\t'Form',\n\t\t'Shim.Configure',\n\t\t'SomeInvalidOneWillBeIgnored',\n\t];\n\n}\n"
  },
  {
    "path": "tests/test_app/templates/Foos/anonymous.php",
    "content": "<?php\n/**\n * @var \\Cake\\View\\View $this\n * @var array<\\App\\Model\\Entity\\ParticipantMood> $participantMoods\n */\n?>\n<div>\n\t<?php\n\t// This should NOT trigger an annotation for $m\n\t$yourMoodIds = array_map(function($m) { return $m->mood_id; }, $participantMoods ?? []);\n\n\t// This should NOT trigger an annotation for $item\n\t$filtered = array_filter($items, function($item) {\n\t\treturn $item->active;\n\t});\n\n\t// This should NOT trigger annotations for $a and $b\n\tusort($data, function($a, $b) {\n\t\treturn $a->sort_order <=> $b->sort_order;\n\t});\n\n\t// Arrow function - should NOT trigger annotation for $x\n\t$doubled = array_map(fn($x) => $x * 2, $numbers);\n\n\t// Variables with 'use' clause - $multiplier should still get annotation\n\t$multiplier = 5;\n\t$result = array_map(function($n) use ($multiplier) {\n\t\treturn $n * $multiplier;\n\t}, $values);\n\t?>\n\n\t<ul>\n\t\t<?php foreach ($yourMoodIds as $id): ?>\n\t\t\t<li><?= h($id) ?></li>\n\t\t<?php endforeach; ?>\n\t</ul>\n</div>"
  },
  {
    "path": "tests/test_app/templates/Foos/array.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var array $x\n * @var array<int> $ints\n * @var array{a: int, b: string|null}|null $shaped\n */\n\tforeach ($x as $y) {\n\t\techo $y;\n\t}\n\tforeach ($foo as $int) {\n\t\techo $int;\n\t}\n?>\n<div>\n\t<?php foreach ($ints as $int) {\n\t\techo $int;\n\t} ?>\n\t<?php foreach ($shaped as $x) {\n\t\techo h($x);\n\t} ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/custom_view.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\CustomView $this\n */\n?>\n<div>\n\t<?php foreach ($cars as $car) {} ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/edit.php",
    "content": "<div>\n\t<?php echo $this->Form->create($foo, []); ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/empty.php",
    "content": ""
  },
  {
    "path": "tests/test_app/templates/Foos/existing.php",
    "content": "<?php\n/**\n * @license MIT\n */\n\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var array<int, int> $things\n */\n?>\n<div>\n\t<?php foreach ($cars as $car) {} ?>\n\t<?php echo h($wheel->id); ?>\n\n\t<?php foreach ($things as $keyInt => $valueInt) {\n\t\techo h($keyInt + $valueInt);\n\t} ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/existing_strict.php",
    "content": "<?php declare(strict_types=1);\n/**\n * @license MIT\n */\n\n/**\n * @var \\TestApp\\View\\AppView $this\n */\n?>\n<div>\n\t<?php echo h($wheel->id); ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/following_inline.php",
    "content": "<?php\n/**\n * @license MIT\n * @var \\TestApp\\View\\AppView $this\n */\n\n/** @var \\Authorization\\Identity $identity */\n$identity = $this->getRequest()\n\t->getAttribute('identity');\n?>\n<div>\n\t<?php echo h($wheel->id); ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/inline.php",
    "content": "<?php\n/** @var \\TestApp\\Model\\Entity\\Wheel $wheel */\n?>\n<div>\n\t<?php echo h($wheel->id); ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/loop.php",
    "content": "<?php\nuse Something;\n?>\n<div>\n\t<?php foreach ($cars as $car) {} ?>\n\t<?php echo h($wheel->id); ?>\n\n\t<?php foreach ($wheel->foos as $foos): ?>\n\t\t<tr>\n\t\t\t<td><?= h($foos->x) ?></td>\n\t\t</tr>\n\t<?php endforeach; ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/multiline.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var array $x\n * @var array<int> $ints\n * @var array{\n * \t\ta: int,\n * \t\tb: string|null\n * }|null $shaped\n * @var array{\n * \t\tc: array{\n * \t\t\td: int|string,\n * \t\t\te: string|null\n * \t\t}\n * } $nested\n */\n\tforeach ($x as $y) {\n\t\techo $y;\n\t}\n\tforeach ($foo as $int) {\n\t\techo $int;\n\t}\n?>\n<div>\n\t<?php foreach ($ints as $int) {\n\t\techo $int;\n\t} ?>\n\t<?php foreach ($shaped as $x) {\n\t\techo h($x);\n\t} ?>\n\t<?php foreach ($nested['c'] as $subArray) {\n\t\techo h($x);\n\t} ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/outdated.php",
    "content": "<?php\n/**\n * @var \\Cake\\View\\View $this\n * @var \\Cake\\ORM\\Entity $car !\n * @var \\Cake\\ORM\\Entity[]|\\Cake\\Collection\\CollectionInterface $wheels\n * @var bool $bool\n * @var \\Really\\Outdated $outdated\n */\n?>\n<div>\n\t<?php echo $this->Form->sth(); ?>\n\t<?php echo $car->id; ?>\n\t<?php foreach ($wheels as $wheel); ?>\n\n\t<?php echo $bool ? 'yes' : 'no'; ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/phpline.php",
    "content": "<?php echo $this->MyHelper->foo(); ?>\n<div>\n\t<?php foreach ($cars as $car) {} ?>\n\t<?php echo h($wheel->id); ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/string_interpolation.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n */\n$url = \"/some/path?param={$this->request->getData('field')}\";\n?>\n<div>\n\t<a href=\"<?php echo $url; ?>\">Test</a>\n\t<?php echo h($name); ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Foos/vars.php",
    "content": "<?php\n\tif ($obj) {\n\t\techo $obj->foo();\n\t}\n\techo $this->Generator->generate(['obj' => $obj]);\n?>\n<div>\n\t<?php foreach ($allCars as $car) {\n\t\t$finalCarTime = $this->Helper->out($car->created);\n\t\techo $finalCarTime;\n\t} ?>\n\t<?php echo h($wheel->id); ?>\n\t<p>\n\t\t<?= $date->format(); ?>\n\t</p>\n\t<?php foreach ($allCars->engines as $i => $engine) {\n\t\techo h($engine);\n\t} ?>\n\t<?php foreach ($foos as $foo) {\n\t\techo h($foo->prop);\n\t} ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/Helpers/helpers.php",
    "content": "<div>\n\t<?php echo $this->My->foo($bar); ?>\n\t<?php if ($this->Configure->baz()) {} ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/View/view.php",
    "content": "<div>\n\t<?php echo $this->My->foo($bar); ?>\n\t<?php if ($this->Configure->baz()) {} ?>\n</div>\n"
  },
  {
    "path": "tests/test_app/templates/element/deeply/nested.php",
    "content": ""
  },
  {
    "path": "tests/test_app/templates/element/example.php",
    "content": ""
  },
  {
    "path": "tests/test_app/templates/layout/ajax.php",
    "content": "<?php\n"
  },
  {
    "path": "tests/test_app/tests/Fixture/SmallWindowsFixture.php",
    "content": "<?php\n\nnamespace TestApp\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass SmallWindowsFixture extends TestFixture {\n\n\t/**\n\t * Fields\n\t */\n\tpublic array $fields = [\n\t\t'id' => ['type' => 'integer', 'length' => 11, 'unsigned' => false, 'null' => false, 'default' => null, 'comment' => '', 'autoIncrement' => true, 'precision' => null],\n\t\t'name' => ['type' => 'string', 'length' => 255, 'null' => false, 'default' => null, 'comment' => '', 'precision' => null, 'fixed' => null],\n\t\t'_constraints' => [\n\t\t\t'primary' => ['type' => 'primary', 'columns' => ['id'], 'length' => []],\n\t\t],\n\t\t'_options' => [\n\t\t\t'engine' => 'InnoDB',\n\t\t\t'collation' => 'utf8_general_ci',\n\t\t],\n\t];\n\n\t/**\n\t * Records\n\t */\n\tpublic array $records = [\n\t\t[\n\t\t\t'id' => 1,\n\t\t\t'name' => 'Lorem ipsum dolor sit amet',\n\t\t],\n\t];\n\n}\n"
  },
  {
    "path": "tests/test_app/vendor/cakephp/cakephp/tests/Fixture/PostsFixture.php",
    "content": "<?php\n\nnamespace Cake\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass PostsFixture extends TestFixture\n{\n    /**\n     * @var array\n     */\n    public array $records = [\n        ['author_id' => 1, 'title' => 'First Post', 'body' => 'First Post Body', 'published' => 'Y'],\n        ['author_id' => 3, 'title' => 'Second Post', 'body' => 'Second Post Body', 'published' => 'Y'],\n        ['author_id' => 1, 'title' => 'Third Post', 'body' => 'Third Post Body', 'published' => 'Y'],\n    ];\n}\n"
  },
  {
    "path": "tests/test_files/ClassAnnotation/TableFind/before.php",
    "content": "<?php\n\nnamespace App\\Controller;\n\nuse Cake\\ORM\\Locator\\LocatorAwareTrait;\n\n/** @property \\Cake\\ORM\\Table $Residents */\nclass TestMeController\n{\n\tuse LocatorAwareTrait;\n\n\tpublic function test(): void {\n\t\t$residentsTable = $this->fetchTable('Residents');\n\t\t$resident = $residentsTable->find(\n\t\t\t'all',\n\t\t\t[\n\t\t\t\t'contain' => ['Units', 'Rooms'],\n\t\t\t],\n\t\t)->first();\n\n\t\t$residentOther = $residentsTable->find(\n\t\t\t'all',\n\t\t\t[\n\t\t\t\t'contain' => ['Units', 'Rooms'],\n\t\t\t],\n\t\t)->firstOrFail();\n\n\t\t$residentX = $this->Residents->find(\n\t\t\t'all',\n\t\t\t[\n\t\t\t\t'contain' => ['Units', 'Rooms'],\n\t\t\t],\n\t\t)->first();\n\n\t\t$residentY = $this->Residents->find(\n\t\t\t'all',\n\t\t\t[\n\t\t\t\t'contain' => ['Units', 'Rooms'],\n\t\t\t],\n\t\t)->firstOrFail();\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Command/MyCommand.php",
    "content": "<?php\nnamespace TestApp\\Command;\n\nuse Shim\\Command\\Command;\n\n/**\n * @property \\TestApp\\Model\\Table\\WheelsTable $Wheels\n * @property \\TestApp\\Model\\Table\\CarsTable $Cars\n */\nclass MyCommand extends Command {\n\n\tprotected ?string $defaultTable = 'Cars';\n\n\t/** @var \\Relations\\Model\\Table\\BarsTable */\n\tprotected $Bars;\n\n\t/**\n\t * @return void\n\t */\n\tpublic function main() {\n\t\t$this->Wheels = $this->fetchTable('Wheels');\n\t\t$this->Bars = $this->fetchTable('Relations.Bars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Controller/AppController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\nuse Cake\\Controller\\Controller;\n\n/**\n * @property \\TestApp\\Controller\\Component\\MyOtherComponent $MyOther\n * @property \\TestApp\\Controller\\Component\\CheckHttpCacheComponent $CheckHttpCache\n */\nclass AppController extends Controller {\n\n\t/**\n\t * @return void\n\t */\n\tpublic function initialize(): void {\n\t\tparent::initialize();\n\n\t\t$this->loadComponent('Flash');\n\t\t$this->loadComponent('CheckHttpCache');\n\t\t$this->loadComponent('MyOther');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Controller/BarController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\nuse TestApp\\Model\\Table\\WheelsTable;\n\n/**\n * @property \\TestApp\\Model\\Table\\BarBarsTable $BarBars\n * @property \\MyNamespace\\MyPlugin\\Controller\\Component\\MyComponent $My\n *\n * @method \\TestApp\\Model\\Entity\\BarBar[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar> paginate(\\Cake\\Datasource\\RepositoryInterface|\\Cake\\Datasource\\QueryInterface|string|null $object = null, array $settings = [])\n */\nclass BarController extends AppController {\n\n\tprotected ?string $defaultTable = 'BarBars';\n\n\tprotected WheelsTable $Wheels;\n\n\t/**\n\t * @return void\n\t */\n\tpublic function initialize(): void {\n\t\tparent::initialize();\n\n\t\t$this->loadComponent('Flash');\n\t\t$this->loadComponent('MyNamespace/MyPlugin.My');\n\n\t\t$this->Wheels = $this->fetchTable('Wheels');\n\t}\n\n\t/**\n\t * @return \\Cake\\Http\\Response|null|void\n\t */\n\tpublic function index() {\n\t\t$this->paginate($this->BarBars);\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Controller/Component/MyComponent.php",
    "content": "<?php\nnamespace TestApp\\Controller\\Component;\n\nuse Cake\\Controller\\Component;\n\n/**\n * @property \\Cake\\Controller\\Component\\FlashComponent $Flash\n * @property \\TestApp\\Controller\\Component\\CheckHttpCacheComponent $CheckHttpCache\n * @property \\MyNamespace\\MyPlugin\\Controller\\Component\\MyComponent $My\n */\nclass MyComponent extends Component {\n\n\tpublic array $components = ['Flash', 'CheckHttpCache', 'MyNamespace/MyPlugin.My'];\n\n}\n"
  },
  {
    "path": "tests/test_files/Controller/Component/MyControllerComponent.php",
    "content": "<?php\nnamespace TestApp\\Controller\\Component;\n\nuse Cake\\Controller\\Component;\nuse Cake\\Event\\EventInterface;\n\n/**\n * @method \\TestApp\\Controller\\AppController getController()\n */\nclass MyControllerComponent extends Component {\n\n\t/**\n\t * @param \\Cake\\Event\\EventInterface $event\n\t *\n\t * @return void\n\t */\n\tpublic function beforeFilter(EventInterface $event): void {\n\t\t$user = (bool)$this->getController()->AuthUser->user();\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Controller/Component/MyOtherComponent.php",
    "content": "<?php\nnamespace TestApp\\Controller\\Component;\n\nuse Cake\\Controller\\Component;\n\n/**\n * @property \\TestApp\\Controller\\Component\\CheckHttpCacheComponent $CheckHttpCache\n * @property \\Cake\\Controller\\Component\\FormProtectionComponent $FormProtection\n * @property \\Cake\\Controller\\Component\\FlashComponent $Flash\n */\nclass MyOtherComponent extends Component {\n\n\tpublic array $components = ['Flash', 'CheckHttpCache', 'SomeInvalidOneWillBeIgnored', 'FormProtection'];\n\n}\n"
  },
  {
    "path": "tests/test_files/Controller/DynamicPropertiesController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\n/**\n * @property \\TestApp\\Model\\Table\\BarBarsTable $BarBars\n */\n#[\\AllowDynamicProperties]\nclass DynamicPropertiesController extends AppController {\n\n\tprotected ?string $defaultTable = 'BarBars';\n\n}\n"
  },
  {
    "path": "tests/test_files/Controller/DynamicPropertiesExistingDocblockController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\n/**\n * Existing docblock\n *\n * @property \\TestApp\\Model\\Table\\BarBarsTable $BarBars\n */\n#[\\AllowDynamicProperties]\nclass DynamicPropertiesExistingDocblockController extends AppController {\n\n\tprotected ?string $defaultTable = 'BarBars';\n\n}\n"
  },
  {
    "path": "tests/test_files/Controller/FoosController.php",
    "content": "<?php\nnamespace TestApp\\Controller;\n\n/**\n * @property \\TestApp\\Model\\Table\\FoosTable $Foos\n */\nclass FoosController extends AppController {\n}\n"
  },
  {
    "path": "tests/test_files/Controller/HousesController.php",
    "content": "<?php\nnamespace Controllers\\Controller;\n\nuse Cake\\Controller\\Controller;\n\n/**\n * @property \\Controllers\\Model\\Table\\HousesTable $Houses\n */\nclass HousesController extends Controller {\n}\n"
  },
  {
    "path": "tests/test_files/Controller/WindowsController.php",
    "content": "<?php\nnamespace Controllers\\Controller;\n\nuse Cake\\Controller\\Controller;\n\n/**\n * @property \\Awesome\\Model\\Table\\WindowsTable $Windows\n */\nclass WindowsController extends Controller {\n\n\tprotected ?string $defaultTable = 'Awesome.Windows';\n\n}\n"
  },
  {
    "path": "tests/test_files/Custom/CustomClass.php",
    "content": "<?php\nnamespace TestApp\\Custom;\n\nuse Cake\\Datasource\\ModelAwareTrait;\n\n/**\n * @property \\TestApp\\Model\\Table\\FoosTable $Foos\n */\nclass CustomClass {\n\n\tuse ModelAwareTrait;\n\n\t/**\n\t * @return void\n\t */\n\tpublic function initialize() {\n\t\t$this->fetchModel('Foos');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Custom/Nested/NestedClass.php",
    "content": "<?php\nnamespace TestApp\\Custom\\Nested;\n\nuse Cake\\Datasource\\ModelAwareTrait;\n\n/**\n * @property \\TestApp\\Model\\Table\\FoosTable $Foos\n */\nclass NestedClass {\n\n\tuse ModelAwareTrait;\n\n\t/**\n\t * @return void\n\t */\n\tpublic function initialize() {\n\t\t$this->loadModel('Foos');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/FormAnnotation/FormAnnotation.existing.php",
    "content": "<?php\nnamespace TestApp\\Foo;\n\nuse TestApp\\Form\\DocForm;\n\nclass FormAnnotation {\n\n\tpublic function test() {\n\t\t$docForm = new DocForm();\n\t\t/** @uses \\TestApp\\Form\\DocForm::_execute() */\n\t\t$docForm->execute();\n\t}\n}\n"
  },
  {
    "path": "tests/test_files/FormAnnotation/FormAnnotation.missing.php",
    "content": "<?php\nnamespace TestApp\\Foo;\n\nuse TestApp\\Form\\DocForm;\n\nclass FormAnnotation {\n\n\tpublic function test() {\n\t\t$docForm = new DocForm();\n\t\t$docForm->execute();\n\t}\n}\n"
  },
  {
    "path": "tests/test_files/MailerAnnotation/MailerAnnotation.existing.php",
    "content": "<?php\nnamespace TestApp\\Foo;\n\nuse TestApp\\Mailer\\NotificationMailer;\n\nclass MailerAnnotation {\n\n\tpublic function test($notificationMailer) {\n\t\t/** @uses \\TestApp\\Mailer\\NotificationMailer::notify() */\n\t\t$notificationMailer->send('notify', []);\n\t}\n}\n"
  },
  {
    "path": "tests/test_files/MailerAnnotation/MailerAnnotation.invalid.php",
    "content": "<?php\nnamespace TestApp\\Mailer;\n\nclass DebugMailer {\n\n\t/**\n\t * Usage: $this->getMailer('Debug')->send('test');\n\t *\n\t * @return self\n\t */\n\tpublic function test(): self {\n\t\t$this->setTransport('gmail')\n\t\t\t->setTo('info@pfiff.me')\n\t\t\t->setSubject('Debug Email')\n\t\t\t->setEmailFormat('html')\n\t\t\t->viewBuilder()\n\t\t\t->setTemplate('default');\n\n\t\treturn $this;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/MailerAnnotation/MailerAnnotation.missing.php",
    "content": "<?php\nnamespace TestApp\\Foo;\n\nclass MailerAnnotation {\n\n\tpublic function test() {\n\t\t$notificationMailer = $this->getMailer('Notification');\n\n\t\t$notificationMailer->send('notify', []);\n\t}\n}\n"
  },
  {
    "path": "tests/test_files/MailerAnnotation/MailerAnnotation.missing2.php",
    "content": "<?php\nnamespace TestApp\\Foo;\n\nclass MailerAnnotation {\n\n\tpublic function test() {\n\t\t$this->getMailer('Notification')->send('notify', []);\n\t}\n}\n"
  },
  {
    "path": "tests/test_files/MailerAnnotation/MailerAnnotation.missing3.php",
    "content": "<?php\nnamespace TestApp\\Foo;\n\nclass MailerAnnotation {\n\n\tpublic function test() {\n\t\t$this->getMailer('Notification')\n\t\t\t->send('notify', []);\n\t}\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Car.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * My car entity which is very cool.\n *\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property \\TestApp\\Model\\Enum\\CarStatus $status\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n */\nclass Car extends Entity {\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Complex/Wheel.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array{user?: int, account?: int|string, newContacts?: array<mixed>}|null $params !\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n *\n * @property-read string|null $virtual_one\n * @property \\TestApp\\Model\\Entity\\Car $car\n */\nclass Wheel extends Entity {\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Constants/Wheel.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array{user?: int, account?: int|string, newContacts?: array<mixed>}|null $params !\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\Date $offer_date\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n *\n * @property-read string|null $virtual_one\n */\nclass Wheel extends Entity {\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n\tconst FIELD_PARAMS = 'params';\n\tconst FIELD_ID = 'id';\n\tconst FIELD_NAME = 'name';\n\tconst FIELD_CONTENT = 'content';\n\tconst FIELD_OFFER_DATE = 'offer_date';\n\tconst FIELD_CREATED = 'created';\n\tconst FIELD_MODIFIED = 'modified';\n\tconst FIELD_WHEELS = 'wheels';\n\tconst FIELD_VIRTUAL_ONE = 'virtual_one';\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Constants/WheelComplex.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array{utm_tags: array<mixed>|null}|null $params !\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\Date $offer_date\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n *\n * @property-read string|null $virtual_one\n */\nclass Wheel extends Entity {\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n\tconst FIELD_PARAMS = 'params';\n\tconst FIELD_ID = 'id';\n\tconst FIELD_NAME = 'name';\n\tconst FIELD_CONTENT = 'content';\n\tconst FIELD_OFFER_DATE = 'offer_date';\n\tconst FIELD_CREATED = 'created';\n\tconst FIELD_MODIFIED = 'modified';\n\tconst FIELD_WHEELS = 'wheels';\n\tconst FIELD_VIRTUAL_ONE = 'virtual_one';\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/ConstantsPartial/Wheel.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property-read string|null $virtual_one\n * @property mixed $virtual_two\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n */\nclass Wheel extends Entity {\n\n\tconst OTHER_NAME = 'unrelated';\n\n\tconst FIELD_ID = 'id';\n\tconst FIELD_NAME = 'name';\n\tconst FIELD_CONTENT = 'content';\n\tconst FIELD_CREATED = 'created';\n\tconst FIELD_WHEELS = 'wheels';\n\tconst FIELD_VIRTUAL_TWO = 'virtual_two';\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n\tprotected function _getVirtualTwo() {\n\t\t// Missing return type and docblock means mixed as result\n\t\treturn 'Virtual Two';\n\t}\n\n\t/**\n\t * @param \\TestApp\\Model\\Entity\\Wheel[] $wheels\n\t *\n\t * @return \\TestApp\\Model\\Entity\\Wheel[]\n\t */\n\tprotected function _getWheels($wheels = []) {\n\t\treturn $wheels;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/ConstantsPartialResult/Wheel.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property-read string|null $virtual_one\n * @property mixed $virtual_two\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n */\nclass Wheel extends Entity {\n\n\tconst OTHER_NAME = 'unrelated';\n\n\tconst FIELD_ID = 'id';\n\tconst FIELD_NAME = 'name';\n\tconst FIELD_CONTENT = 'content';\n\tconst FIELD_CREATED = 'created';\n\tconst FIELD_WHEELS = 'wheels';\n\tconst FIELD_VIRTUAL_TWO = 'virtual_two';\n\tconst FIELD_MODIFIED = 'modified';\n\tconst FIELD_VIRTUAL_ONE = 'virtual_one';\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n\tprotected function _getVirtualTwo() {\n\t\t// Missing return type and docblock means mixed as result\n\t\treturn 'Virtual Two';\n\t}\n\n\t/**\n\t * @param \\TestApp\\Model\\Entity\\Wheel[] $wheels\n\t *\n\t * @return \\TestApp\\Model\\Entity\\Wheel[]\n\t */\n\tprotected function _getWheels($wheels = []) {\n\t\treturn $wheels;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Foo.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array|null $params\n * @property int $id\n * @property \\Cake\\I18n\\DateTime $created\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\Date $offer_date\n * @property \\Cake\\I18n\\DateTime|null $modified\n */\nclass Foo extends Entity {\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/PHP/ComplexType.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity\\PHP;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property array{foo: array<mixed>|null}|null $params !\n * @property \\Cake\\I18n\\Date $offer_date\n */\nclass ComplexType extends Entity {\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/PHP/Duplicates.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity\\PHP;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property \\Cake\\I18n\\Date $offer_date\n * @property array|null $params\n */\nclass Duplicates extends Entity {\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/PHP/Generics.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity\\PHP;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array|null $params\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property array<\\TestApp\\Model\\Entity\\Wheel> $wheels\n * @property \\Cake\\I18n\\Date $offer_date\n */\nclass Generics extends Entity {\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/PHP7/Virtual.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity\\PHP7;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array|null $params\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\Date $offer_date\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property string $virtual_two\n * @property array<int, array<string>> $nested_generic\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n *\n * @property-read string|null $virtual_one\n */\nclass Virtual extends Entity {\n\n\tprotected function _getVirtualOne(): ?string {\n\t\treturn 'Virtual One';\n\t}\n\n\tprotected function _getVirtualTwo(): string {\n\t\treturn 'Virtual Two';\n\t}\n\n\t/**\n\t * @return array<int, array<string>>\n\t */\n\tprotected function _getNestedGeneric(): array {\n\t\treturn [[1 => ['a', 'b']]];\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Relations/Bar.php",
    "content": "<?php\nnamespace Relations\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property int $id\n * @property string $name\n * @property int|null $user_id\n * @property \\Relations\\Model\\Entity\\User|null $user\n */\nclass Bar extends Entity {\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Relations/Foo.php",
    "content": "<?php\nnamespace Relations\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array|null $params\n * @property int $id\n * @property string $name\n * @property int $user_id\n * @property \\Relations\\Model\\Entity\\User $user\n */\nclass Foo extends Entity {\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Relations/User.php",
    "content": "<?php\nnamespace Relations\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property int $id\n * @property string $name\n * @property \\Relations\\Model\\Entity\\Foo|null $foo\n * @property \\Relations\\Model\\Entity\\Bar|null $bar\n */\nclass User extends Entity {\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Virtual.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property-read bool $virtual_read_only This should be kept.\n * @property array|null $params\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\Date $offer_date\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property mixed $virtual_two\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n * @property-read string|null $virtual_one\n */\nclass Virtual extends Entity {\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n\tprotected function _getVirtualTwo() {\n\t\t// Missing return type and docblock means mixed as result\n\t\treturn 'Virtual Two';\n\t}\n\n\t/**\n\t * @return bool\n\t */\n\tprotected function _getVirtualReadOnly() {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param \\TestApp\\Model\\Entity\\Wheel[] $wheels\n\t *\n\t * @return \\TestApp\\Model\\Entity\\Wheel[]\n\t */\n\tprotected function _getWheels($wheels = []) {\n\t\treturn $wheels;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Entity/Wheel.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nuse Cake\\ORM\\Entity;\n\n/**\n * @property array|null $params\n * @property int $id\n * @property string $name\n * @property string $content\n * @property \\Cake\\I18n\\Date $offer_date\n * @property \\Cake\\I18n\\DateTime $created\n * @property \\Cake\\I18n\\DateTime|null $modified\n * @property \\TestApp\\Model\\Entity\\Wheel[] $wheels\n *\n * @property-read string|null $virtual_one\n */\nclass Wheel extends Entity {\n\n\tprotected array $_virtual = [\n\t\t'virtual_one',\n\t];\n\n\t/**\n\t * @return string|null\n\t */\n\tprotected function _getVirtualOne() {\n\t\treturn 'Virtual One';\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/BarBarsAbstractTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\FoosTable> $Foos\n * @property \\Cake\\ORM\\Association\\BelongsToMany<\\Awesome\\Model\\Table\\HousesTable> $Houses\n *\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract newEmptyEntity()\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract newEntity(array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract[] newEntities(array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, ?callable $callback = null, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract patchEntity(\\Cake\\Datasource\\EntityInterface $entity, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract[] patchEntities(iterable $entities, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract|false save(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract saveOrFail(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBarsAbstract>|false saveMany(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBarsAbstract> saveManyOrFail(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBarsAbstract>|false deleteMany(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBarsAbstract> deleteManyOrFail(iterable $entities, array $options = [])\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n * @mixin \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n *\n * @extends \\TestApp\\Model\\Table\\AbstractTable<array{MyMy: \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior, Timestamp: \\Cake\\ORM\\Behavior\\TimestampBehavior}>\n */\nclass BarBarsAbstractTable extends AbstractTable {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('bar_bars');\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('MyMy', [\n\t\t\t'className' => 'MyNamespace/MyPlugin.My',\n\t\t]);\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/BarBarsDetailedTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\FoosTable> $Foos\n * @property \\Cake\\ORM\\Association\\BelongsToMany<\\Awesome\\Model\\Table\\HousesTable> $Houses\n *\n * @method \\TestApp\\Model\\Entity\\BarBar newEmptyEntity()\n * @method \\TestApp\\Model\\Entity\\BarBar newEntity(array<string, mixed> $data, array<string, mixed> $options = [])\n * @method array<\\TestApp\\Model\\Entity\\BarBar> newEntities(array<array<string, mixed>> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar get(mixed $primaryKey, array<string, mixed>|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\BarBar findOrCreate(\\Cake\\ORM\\Query\\SelectQuery<\\TestApp\\Model\\Entity\\BarBar>|callable|array<string, mixed> $search, ?callable $callback = null, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar patchEntity(\\Cake\\Datasource\\EntityInterface $entity, array<string, mixed> $data, array<string, mixed> $options = [])\n * @method array<\\TestApp\\Model\\Entity\\BarBar> patchEntities(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<array<string, mixed>> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar|false save(\\Cake\\Datasource\\EntityInterface $entity, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar saveOrFail(\\Cake\\Datasource\\EntityInterface $entity, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<int, \\TestApp\\Model\\Entity\\BarBar>|false saveMany(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<int, \\TestApp\\Model\\Entity\\BarBar> saveManyOrFail(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<int, \\TestApp\\Model\\Entity\\BarBar>|false deleteMany(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<int, \\TestApp\\Model\\Entity\\BarBar> deleteManyOrFail(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n * @mixin \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n *\n * @extends \\Cake\\ORM\\Table<array{My: \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior, Timestamp: \\Cake\\ORM\\Behavior\\TimestampBehavior}>\n */\nclass BarBarsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/BarBarsEntityTemplateTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\FoosTable> $Foos\n * @property \\Cake\\ORM\\Association\\BelongsToMany<\\Awesome\\Model\\Table\\HousesTable> $Houses\n *\n * @method \\TestApp\\Model\\Entity\\BarBar newEmptyEntity()\n * @method \\TestApp\\Model\\Entity\\BarBar newEntity(array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[] newEntities(array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\BarBar findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, ?callable $callback = null, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar patchEntity(\\Cake\\Datasource\\EntityInterface $entity, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[] patchEntities(iterable $entities, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar|false save(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar saveOrFail(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar>|false saveMany(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar> saveManyOrFail(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar>|false deleteMany(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar> deleteManyOrFail(iterable $entities, array $options = [])\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n * @mixin \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n *\n * @extends \\Cake\\ORM\\Table<array{My: \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior, Timestamp: \\Cake\\ORM\\Behavior\\TimestampBehavior}, \\TestApp\\Model\\Entity\\BarBar>\n */\nclass BarBarsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/BarBarsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\FoosTable> $Foos\n * @property \\Cake\\ORM\\Association\\BelongsToMany<\\Awesome\\Model\\Table\\HousesTable> $Houses\n *\n * @method \\TestApp\\Model\\Entity\\BarBar newEmptyEntity()\n * @method \\TestApp\\Model\\Entity\\BarBar newEntity(array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[] newEntities(array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\BarBar findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, ?callable $callback = null, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar patchEntity(\\Cake\\Datasource\\EntityInterface $entity, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[] patchEntities(iterable $entities, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar|false save(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar saveOrFail(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar>|false saveMany(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar> saveManyOrFail(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar>|false deleteMany(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar> deleteManyOrFail(iterable $entities, array $options = [])\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n * @mixin \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n *\n * @extends \\Cake\\ORM\\Table<array{My: \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior, Timestamp: \\Cake\\ORM\\Behavior\\TimestampBehavior}>\n */\nclass BarBarsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/CallbacksTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse ArrayObject;\nuse Cake\\Datasource\\EntityInterface;\nuse Cake\\Event\\Event;\nuse Cake\\Event\\EventInterface;\nuse Cake\\ORM\\Table;\n\nclass CallbacksTable extends Table {\n\n\t/**\n\t * @param \\Cake\\Event\\Event $event Event\n\t * @param \\TestApp\\Model\\Entity\\Callback $entity Entity\n\t * @param \\ArrayObject $options Options\n\t * @return void\n\t */\n\tpublic function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n\t/**\n\t * @param \\Cake\\Event\\EventInterface $event\n\t * @param \\TestApp\\Model\\Entity\\Callback $entity\n\t * @param \\ArrayObject $options\n\t * @return void\n\t */\n\tpublic function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n\tpublic function beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/FooTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @method \\TestApp\\Model\\Entity\\Foo get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\Foo newEntity($data = null, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo[] newEntities(array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo|false save(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo saveOrFail(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo patchEntity(\\Cake\\Datasource\\EntityInterface $entity, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo[] patchEntities($entities, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, callable $callback = null, array $options = [])\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n * @mixin \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n */\nclass FooTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('BarBars');\n\t\t$this->belongsTo('Houses', [\n\t\t\t'className' => 'Awesome.Houses'\n\t\t]);\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/SkipMeTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @inheritDoc\n */\nclass SkipMeTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/SkipSomeTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $Cars\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $CarsAwesome\n */\nclass SkipSomeTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function foo() {\n\t\t$this->CarsAwesome->doSthAwesome();\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Specific/BarBarsAbstractTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\FoosTable> $Foos\n * @property \\Cake\\ORM\\Association\\BelongsToMany<\\Awesome\\Model\\Table\\HousesTable> $Houses\n *\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract newEmptyEntity()\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract newEntity(array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract[] newEntities(array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, ?callable $callback = null, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract patchEntity(\\TestApp\\Model\\Entity\\BarBarsAbstract $entity, array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract[] patchEntities(iterable<\\TestApp\\Model\\Entity\\BarBarsAbstract> $entities, array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract|false save(\\TestApp\\Model\\Entity\\BarBarsAbstract $entity, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBarsAbstract saveOrFail(\\TestApp\\Model\\Entity\\BarBarsAbstract $entity, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBarsAbstract>|false saveMany(iterable<\\TestApp\\Model\\Entity\\BarBarsAbstract> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBarsAbstract> saveManyOrFail(iterable<\\TestApp\\Model\\Entity\\BarBarsAbstract> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBarsAbstract>|false deleteMany(iterable<\\TestApp\\Model\\Entity\\BarBarsAbstract> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBarsAbstract> deleteManyOrFail(iterable<\\TestApp\\Model\\Entity\\BarBarsAbstract> $entities, array<string, mixed> $options = [])\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n * @mixin \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n */\nclass BarBarsAbstractTable extends AbstractTable {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('bar_bars');\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('MyMy', [\n\t\t\t'className' => 'MyNamespace/MyPlugin.My',\n\t\t]);\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Specific/BarBarsDetailedTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\FoosTable> $Foos\n * @property \\Cake\\ORM\\Association\\BelongsToMany<\\Awesome\\Model\\Table\\HousesTable> $Houses\n *\n * @method \\TestApp\\Model\\Entity\\BarBar newEmptyEntity()\n * @method \\TestApp\\Model\\Entity\\BarBar newEntity(array<string, mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[] newEntities(array<array<string, mixed>> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar get(mixed $primaryKey, array<string, mixed>|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\BarBar findOrCreate(\\Cake\\ORM\\Query\\SelectQuery<\\TestApp\\Model\\Entity\\BarBar>|callable|array<string, mixed> $search, ?callable $callback = null, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar patchEntity(\\TestApp\\Model\\Entity\\BarBar $entity, array<string, mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[] patchEntities(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<array<string, mixed>> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar|false save(\\TestApp\\Model\\Entity\\BarBar $entity, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar saveOrFail(\\TestApp\\Model\\Entity\\BarBar $entity, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<int, \\TestApp\\Model\\Entity\\BarBar>|false saveMany(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<int, \\TestApp\\Model\\Entity\\BarBar> saveManyOrFail(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<int, \\TestApp\\Model\\Entity\\BarBar>|false deleteMany(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<int, \\TestApp\\Model\\Entity\\BarBar> deleteManyOrFail(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n * @mixin \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n */\nclass BarBarsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Specific/BarBarsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\FoosTable> $Foos\n * @property \\Cake\\ORM\\Association\\BelongsToMany<\\Awesome\\Model\\Table\\HousesTable> $Houses\n *\n * @method \\TestApp\\Model\\Entity\\BarBar newEmptyEntity()\n * @method \\TestApp\\Model\\Entity\\BarBar newEntity(array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[] newEntities(array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\BarBar findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, ?callable $callback = null, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar patchEntity(\\TestApp\\Model\\Entity\\BarBar $entity, array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar[] patchEntities(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar|false save(\\TestApp\\Model\\Entity\\BarBar $entity, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\BarBar saveOrFail(\\TestApp\\Model\\Entity\\BarBar $entity, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar>|false saveMany(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar> saveManyOrFail(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar>|false deleteMany(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\BarBar> deleteManyOrFail(iterable<\\TestApp\\Model\\Entity\\BarBar> $entities, array<string, mixed> $options = [])\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n * @mixin \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n */\nclass BarBarsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Foos');\n\t\t$this->belongsToMany('Houses', [\n\t\t\t'className' => 'Awesome.Houses',\n\t\t\t'through' => 'Awesome.Windows',\n\t\t]);\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Specific/CallbacksTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse ArrayObject;\nuse Cake\\Datasource\\EntityInterface;\nuse Cake\\Event\\Event;\nuse Cake\\Event\\EventInterface;\nuse Cake\\ORM\\Table;\n\nclass CallbacksTable extends Table {\n\n\t/**\n\t * @param \\Cake\\Event\\Event $event Event\n\t * @param \\TestApp\\Model\\Entity\\Callback $entity Entity\n\t * @param \\ArrayObject $options Options\n\t * @return void\n\t */\n\tpublic function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n\t/**\n\t * @param \\Cake\\Event\\EventInterface $event\n\t * @param \\TestApp\\Model\\Entity\\Callback $entity\n\t * @param \\ArrayObject $options\n\t * @return void\n\t */\n\tpublic function afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n\tpublic function beforeDelete(Event $event, EntityInterface $entity, ArrayObject $options) {\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Specific/FooTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @method \\TestApp\\Model\\Entity\\Foo get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\Foo newEntity($data = null, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo[] newEntities(array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo|false save(\\TestApp\\Model\\Entity\\Foo $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo saveOrFail(\\TestApp\\Model\\Entity\\Foo $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo patchEntity(\\TestApp\\Model\\Entity\\Foo $entity, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo[] patchEntities($entities, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Foo findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, callable $callback = null, array $options = [])\n *\n * @mixin \\Cake\\ORM\\Behavior\\TimestampBehavior\n * @mixin \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior\n */\nclass FooTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('BarBars');\n\t\t$this->belongsTo('Houses', [\n\t\t\t'className' => 'Awesome.Houses'\n\t\t]);\n\t\t$this->addBehavior('Timestamp');\n\t\t$this->addBehavior('MyNamespace/MyPlugin.My');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Specific/SkipMeTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @inheritDoc\n */\nclass SkipMeTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Specific/SkipSomeTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $Cars\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $CarsAwesome\n */\nclass SkipSomeTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\t}\n\n\t/**\n\t * @return void\n\t */\n\tpublic function foo() {\n\t\t$this->CarsAwesome->doSthAwesome();\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Specific/WheelsExtraTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $Cars\n */\nclass WheelsExtraTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('wheels');\n\t\t$this->belongsTo('Cars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Specific/WheelsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @method \\TestApp\\Model\\Entity\\Wheel newEntity(array<mixed> $data, array<string, mixed> $options = [])\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $Cars\n * @method \\TestApp\\Model\\Entity\\Wheel newEmptyEntity()\n * @method \\TestApp\\Model\\Entity\\Wheel[] newEntities(array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\Wheel findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, ?callable $callback = null, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel patchEntity(\\TestApp\\Model\\Entity\\Wheel $entity, array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel[] patchEntities(iterable<\\TestApp\\Model\\Entity\\Wheel> $entities, array<mixed> $data, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel|false save(\\TestApp\\Model\\Entity\\Wheel $entity, array<string, mixed> $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel saveOrFail(\\TestApp\\Model\\Entity\\Wheel $entity, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\Wheel>|false saveMany(iterable<\\TestApp\\Model\\Entity\\Wheel> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\Wheel> saveManyOrFail(iterable<\\TestApp\\Model\\Entity\\Wheel> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\Wheel>|false deleteMany(iterable<\\TestApp\\Model\\Entity\\Wheel> $entities, array<string, mixed> $options = [])\n * @method \\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\Wheel> deleteManyOrFail(iterable<\\TestApp\\Model\\Entity\\Wheel> $entities, array<string, mixed> $options = [])\n * @mixin \\Cake\\ORM\\Behavior\\TreeBehavior\n */\nclass WheelsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\n\t\t$this->addBehavior('Tree');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/Validation/IpRulesTable.php",
    "content": "<?php\n\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\nuse Cake\\Validation\\Validator;\n\nclass IpRulesTable extends Table {\n\n\t/**\n\t * @param \\Cake\\Validation\\Validator $validator\n\t * @return \\Cake\\Validation\\Validator\n\t */\n\tpublic function validationDefault(Validator $validator): Validator {\n\t\t$validator->add('allow', 'range', [\n\t\t\t'rule' => ['verifyIpRanges', 'allow'],\n\t\t\t'provider' => 'table',\n\t\t\t'message' => 'Please provide valid ip ranges',\n\t\t]);\n\n\t\t$validator->add('deny', 'range', [\n\t\t\t'rule' => 'verifyDenyRanges',\n\t\t\t'provider' => 'table',\n\t\t]);\n\n\t\t// This should NOT get a link (no table provider)\n\t\t$validator->add('email', 'valid', [\n\t\t\t'rule' => 'email',\n\t\t\t'message' => 'Please provide a valid email',\n\t\t]);\n\n\t\treturn $validator;\n\t}\n\n\t/**\n\t * @param string $value\n\t * @param array $context\n\t * @return bool\n\t */\n\tpublic function verifyIpRanges(string $value, array $context): bool {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $value\n\t * @param array $context\n\t * @return bool\n\t */\n\tpublic function verifyDenyRanges(string $value, array $context): bool {\n\t\treturn true;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/ValidationResult/IpRulesTable.php",
    "content": "<?php\n\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\nuse Cake\\Validation\\Validator;\n\nclass IpRulesTable extends Table {\n\n\t/**\n\t * @param \\Cake\\Validation\\Validator $validator\n\t * @return \\Cake\\Validation\\Validator\n\t */\n\tpublic function validationDefault(Validator $validator): Validator {\n\t\t$validator->add('allow', 'range', [\n\t\t\t/** @link verifyIpRanges() */\n\t\t\t'rule' => ['verifyIpRanges', 'allow'],\n\t\t\t'provider' => 'table',\n\t\t\t'message' => 'Please provide valid ip ranges',\n\t\t]);\n\n\t\t$validator->add('deny', 'range', [\n\t\t\t/** @link verifyDenyRanges() */\n\t\t\t'rule' => 'verifyDenyRanges',\n\t\t\t'provider' => 'table',\n\t\t]);\n\n\t\t// This should NOT get a link (no table provider)\n\t\t$validator->add('email', 'valid', [\n\t\t\t'rule' => 'email',\n\t\t\t'message' => 'Please provide a valid email',\n\t\t]);\n\n\t\treturn $validator;\n\t}\n\n\t/**\n\t * @param string $value\n\t * @param array $context\n\t * @return bool\n\t */\n\tpublic function verifyIpRanges(string $value, array $context): bool {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @param string $value\n\t * @param array $context\n\t * @return bool\n\t */\n\tpublic function verifyDenyRanges(string $value, array $context): bool {\n\t\treturn true;\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/WheelsExtraTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $Cars\n */\nclass WheelsExtraTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->setTable('wheels');\n\t\t$this->belongsTo('Cars');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/Model/Table/WheelsTable.php",
    "content": "<?php\nnamespace TestApp\\Model\\Table;\n\nuse Cake\\ORM\\Table;\n\n/**\n * @method \\TestApp\\Model\\Entity\\Wheel newEntity(array $data, array $options = [])\n * @property \\Cake\\ORM\\Association\\BelongsTo<\\TestApp\\Model\\Table\\CarsTable> $Cars\n * @method \\TestApp\\Model\\Entity\\Wheel newEmptyEntity()\n * @method \\TestApp\\Model\\Entity\\Wheel[] newEntities(array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel get(mixed $primaryKey, array|string $finder = 'all', \\Psr\\SimpleCache\\CacheInterface|string|null $cache = null, \\Closure|string|null $cacheKey = null, mixed ...$args)\n * @method \\TestApp\\Model\\Entity\\Wheel findOrCreate(\\Cake\\ORM\\Query\\SelectQuery|callable|array $search, ?callable $callback = null, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel patchEntity(\\Cake\\Datasource\\EntityInterface $entity, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel[] patchEntities(iterable $entities, array $data, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel|false save(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel saveOrFail(\\Cake\\Datasource\\EntityInterface $entity, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\Wheel>|false saveMany(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\Wheel> saveManyOrFail(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\Wheel>|false deleteMany(iterable $entities, array $options = [])\n * @method \\TestApp\\Model\\Entity\\Wheel[]|\\Cake\\Datasource\\ResultSetInterface<\\TestApp\\Model\\Entity\\Wheel> deleteManyOrFail(iterable $entities, array $options = [])\n * @mixin \\Cake\\ORM\\Behavior\\TreeBehavior\n * @extends \\Cake\\ORM\\Table<array{Tree: \\Cake\\ORM\\Behavior\\TreeBehavior}>\n */\nclass WheelsTable extends Table {\n\n\t/**\n\t * @param array $config\n\t * @return void\n\t */\n\tpublic function initialize(array $config): void {\n\t\tparent::initialize($config);\n\n\t\t$this->belongsTo('Cars');\n\n\t\t$this->addBehavior('Tree');\n\t}\n\n}\n"
  },
  {
    "path": "tests/test_files/View/AppView.php",
    "content": "<?php\nnamespace TestApp\\View;\n\nuse Cake\\View\\View;\n\n/**\n * @link https://book.cakephp.org/5/en/views.html#the-app-view\n * @property \\TestApp\\View\\Helper\\HtmlHelper $Html\n * @property \\TestApp\\View\\Helper\\MyHelper $My\n * @property \\TestApp\\View\\Helper\\MyMethodHelper $MyMethod\n * @property \\Shim\\View\\Helper\\ConfigureHelper $Configure\n */\nclass AppView extends View {\n}\n"
  },
  {
    "path": "tests/test_files/View/Helper/MyHelper.php",
    "content": "<?php\nnamespace TestApp\\View\\Helper;\n\nuse Cake\\View\\Helper;\n\n/**\n * @property \\TestApp\\View\\Helper\\HtmlHelper $Html\n * @property \\Cake\\View\\Helper\\FormHelper $Form\n * @property \\Shim\\View\\Helper\\ConfigureHelper $Configure\n */\nclass MyHelper extends Helper {\n\n\tprotected array $helpers = [\n\t\t'Html',\n\t\t'Form',\n\t\t'Shim.Configure',\n\t\t'SomeInvalidOneWillBeIgnored',\n\t];\n\n}\n"
  },
  {
    "path": "tests/test_files/View/Helper/MyMethodHelper.php",
    "content": "<?php\nnamespace TestApp\\View\\Helper;\n\nuse Cake\\View\\Helper;\n\n/**\n * @property \\Cake\\View\\Helper\\FormHelper $Form\n * @method bool isAdmin()\n * @method bool isStaff()\n * @property \\TestApp\\View\\Helper\\HtmlHelper $Html\n * @property \\Shim\\View\\Helper\\ConfigureHelper $Configure\n */\nclass MyMethodHelper extends Helper {\n\n\tprotected array $helpers = [\n\t\t'Html',\n\t\t'Form',\n\t\t'Shim.Configure',\n\t\t'SomeInvalidOneWillBeIgnored',\n\t];\n\n}\n"
  },
  {
    "path": "tests/test_files/VirtualFieldAnnotation/VirtualFieldAnnotation.existing.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nclass Foo {\n\n\t/**\n\t * @see \\TestApp\\Model\\Entity\\Foo::$expected_release_type\n\t *\n\t * @return int|null\n\t */\n\tprotected function _getExpectedReleaseType(): ?int {\n\t\t$expected = $this->something;\n\n\t\treturn $expected ?? null;\n\t}\n\n\t/**\n\t * @see $something\n\t *\n\t * @return string|null\n\t */\n\tprotected function _getSomething(): ?string {\n\t\t$expected = $this->something;\n\n\t\treturn $expected ?? null;\n\t}\n}\n"
  },
  {
    "path": "tests/test_files/VirtualFieldAnnotation/VirtualFieldAnnotation.missing.php",
    "content": "<?php\nnamespace TestApp\\Model\\Entity;\n\nclass Foo {\n\n\t/**\n\t * @return int|null\n\t */\n\tprotected function _getExpectedReleaseType(): ?int\n\t{\n\t\t$expected = $this->something;\n\n\t\treturn $expected ?? null;\n\t}\n}\n"
  },
  {
    "path": "tests/test_files/locales/default.po",
    "content": "\nmsgid \"A \\\"quoted\\\" string\"\nmsgstr \"Ein \\\"quoted\\\" String\"\n\nmsgid \"A \"\"escape-quoted\"\" string\"\nmsgstr \"Ein \"\"escape-quoted\"\" String\"\n\nmsgid \"A 'literally quoted' string\"\nmsgstr \"Ein 'wörtlich quoted' String\"\n\nmsgid \"A variable ''{0}'' be replaced.\"\nmsgstr \"Eine Variable ''{0}'' ersetzt.\"\n"
  },
  {
    "path": "tests/test_files/meta/phpstorm/.meta.php",
    "content": "<?php\n// @link https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata\nnamespace PHPSTORM_META {\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::add(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::clear(),\n\t\t0,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::clearGroup(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::decrement(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::delete(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::deleteMany(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::increment(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::read(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::readMany(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::remember(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::write(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texitPoint(\\Cake\\Console\\ConsoleIo::abort());\n\n\toverride(\n\t\t\\Cake\\Console\\ConsoleIo::helper(0),\n\t\tmap([\n\t\t\t'Banner' => \\Cake\\Command\\Helper\\BannerHelper::class,\n\t\t\t'Progress' => \\Cake\\Command\\Helper\\ProgressHelper::class,\n\t\t\t'Table' => \\Cake\\Command\\Helper\\TableHelper::class,\n\t\t\t'Tree' => \\Cake\\Command\\Helper\\TreeHelper::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Controller\\ComponentRegistry::unload(),\n\t\t0,\n\t\t'CheckHttpCache',\n\t\t'Flash',\n\t\t'FormProtection',\n\t\t'My',\n\t\t'MyController',\n\t\t'MyOther',\n\t\t'RequestHandler',\n\t);\n\n\toverride(\n\t\t\\Cake\\Controller\\Controller::loadComponent(0),\n\t\tmap([\n\t\t\t'CheckHttpCache' => \\TestApp\\Controller\\Component\\CheckHttpCacheComponent::class,\n\t\t\t'Flash' => \\Cake\\Controller\\Component\\FlashComponent::class,\n\t\t\t'FormProtection' => \\Cake\\Controller\\Component\\FormProtectionComponent::class,\n\t\t\t'My' => \\TestApp\\Controller\\Component\\MyComponent::class,\n\t\t\t'MyController' => \\TestApp\\Controller\\Component\\MyControllerComponent::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Controller\\Component\\MyComponent::class,\n\t\t\t'MyOther' => \\TestApp\\Controller\\Component\\MyOtherComponent::class,\n\t\t\t'Shim.RequestHandler' => \\Shim\\Controller\\Component\\RequestHandlerComponent::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::check(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::consume(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::consumeOrFail(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::delete(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::read(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::readOrFail(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::write(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\toverride(\n\t\t\\Cake\\Core\\PluginApplicationInterface::addPlugin(0),\n\t\tmap([\n\t\t\t'Bake' => \\Cake\\Http\\BaseApplication::class,\n\t\t\t'Cake/TwigView' => \\Cake\\Http\\BaseApplication::class,\n\t\t\t'Migrations' => \\Cake\\Http\\BaseApplication::class,\n\t\t\t'Shim' => \\Cake\\Http\\BaseApplication::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\Database\\TypeFactory::build(0),\n\t\tmap([\n\t\t\t'biginteger' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'binary' => \\Cake\\Database\\Type\\BinaryType::class,\n\t\t\t'binaryuuid' => \\Cake\\Database\\Type\\BinaryUuidType::class,\n\t\t\t'boolean' => \\Cake\\Database\\Type\\BoolType::class,\n\t\t\t'char' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'cidr' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'citext' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'date' => \\Cake\\Database\\Type\\DateType::class,\n\t\t\t'datetime' => \\Cake\\Database\\Type\\DateTimeType::class,\n\t\t\t'datetimefractional' => \\Cake\\Database\\Type\\DateTimeFractionalType::class,\n\t\t\t'decimal' => \\Cake\\Database\\Type\\DecimalType::class,\n\t\t\t'float' => \\Cake\\Database\\Type\\FloatType::class,\n\t\t\t'geometry' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'inet' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'integer' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'json' => \\Cake\\Database\\Type\\JsonType::class,\n\t\t\t'linestring' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'macaddr' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'nativeuuid' => \\Cake\\Database\\Type\\UuidType::class,\n\t\t\t'point' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'polygon' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'smallinteger' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'string' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'text' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'time' => \\Cake\\Database\\Type\\TimeType::class,\n\t\t\t'timestamp' => \\Cake\\Database\\Type\\DateTimeType::class,\n\t\t\t'timestampfractional' => \\Cake\\Database\\Type\\DateTimeFractionalType::class,\n\t\t\t'timestamptimezone' => \\Cake\\Database\\Type\\DateTimeTimezoneType::class,\n\t\t\t'tinyinteger' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'uuid' => \\Cake\\Database\\Type\\UuidType::class,\n\t\t\t'year' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Database\\TypeFactory::map(),\n\t\t0,\n\t\t'biginteger',\n\t\t'binary',\n\t\t'binaryuuid',\n\t\t'boolean',\n\t\t'char',\n\t\t'cidr',\n\t\t'citext',\n\t\t'date',\n\t\t'datetime',\n\t\t'datetimefractional',\n\t\t'decimal',\n\t\t'float',\n\t\t'geometry',\n\t\t'inet',\n\t\t'integer',\n\t\t'json',\n\t\t'linestring',\n\t\t'macaddr',\n\t\t'nativeuuid',\n\t\t'point',\n\t\t'polygon',\n\t\t'smallinteger',\n\t\t'string',\n\t\t'text',\n\t\t'time',\n\t\t'timestamp',\n\t\t'timestampfractional',\n\t\t'timestamptimezone',\n\t\t'tinyinteger',\n\t\t'uuid',\n\t\t'year',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Datasource\\ConnectionManager::get(),\n\t\t0,\n\t\t'test',\n\t);\n\n\toverride(\n\t\t\\Cake\\Datasource\\ModelAwareTrait::fetchModel(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Awesome\\Model\\Table\\HousesTable::class,\n\t\t\t'Awesome.Windows' => \\Awesome\\Model\\Table\\WindowsTable::class,\n\t\t\t'BarBars' => \\TestApp\\Model\\Table\\BarBarsTable::class,\n\t\t\t'BarBarsAbstract' => \\TestApp\\Model\\Table\\BarBarsAbstractTable::class,\n\t\t\t'Callbacks' => \\TestApp\\Model\\Table\\CallbacksTable::class,\n\t\t\t'Cars' => \\TestApp\\Model\\Table\\CarsTable::class,\n\t\t\t'Controllers.Houses' => \\Controllers\\Model\\Table\\HousesTable::class,\n\t\t\t'CustomFinder' => \\TestApp\\Model\\Table\\CustomFinderTable::class,\n\t\t\t'Exceptions' => \\TestApp\\Model\\Table\\ExceptionsTable::class,\n\t\t\t'Foos' => \\TestApp\\Model\\Table\\FoosTable::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class,\n\t\t\t'Relations.Bars' => \\Relations\\Model\\Table\\BarsTable::class,\n\t\t\t'Relations.Foos' => \\Relations\\Model\\Table\\FoosTable::class,\n\t\t\t'Relations.Users' => \\Relations\\Model\\Table\\UsersTable::class,\n\t\t\t'SkipMe' => \\TestApp\\Model\\Table\\SkipMeTable::class,\n\t\t\t'SkipSome' => \\TestApp\\Model\\Table\\SkipSomeTable::class,\n\t\t\t'Wheels' => \\TestApp\\Model\\Table\\WheelsTable::class,\n\t\t\t'WheelsExtra' => \\TestApp\\Model\\Table\\WheelsExtraTable::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\Datasource\\QueryInterface::find(0),\n\t\tmap([\n\t\t\t'all' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'children' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'list' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'path' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'somethingCustom' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'threaded' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'treeList' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\Http\\ServerRequest::getAttribute(0),\n\t\tmap([\n\t\t\t'base' => 'string',\n\t\t\t'cspScriptNonce' => 'string',\n\t\t\t'cspStyleNonce' => 'string',\n\t\t\t'csrfToken' => 'string',\n\t\t\t'formTokenData' => 'array',\n\t\t\t'here' => 'string',\n\t\t\t'paging' => 'array',\n\t\t\t'params' => 'array',\n\t\t\t'route' => \\Cake\\Routing\\Route\\Route::class,\n\t\t\t'session' => \\Cake\\Http\\Session::class,\n\t\t\t'webroot' => 'string',\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Http\\ServerRequest::getParam(),\n\t\t0,\n\t\t'_ext',\n\t\t'_matchedRoute',\n\t\t'action',\n\t\t'controller',\n\t\t'pass',\n\t\t'plugin',\n\t\t'prefix',\n\t);\n\n\toverride(\n\t\t\\Cake\\Mailer\\MailerAwareTrait::getMailer(0),\n\t\tmap([\n\t\t\t'User' => \\TestApp\\Mailer\\UserMailer::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Association::find(0),\n\t\tmap([\n\t\t\t'all' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'children' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'list' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'path' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'somethingCustom' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'threaded' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'treeList' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Locator\\LocatorAwareTrait::fetchTable(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Awesome\\Model\\Table\\HousesTable::class,\n\t\t\t'Awesome.Windows' => \\Awesome\\Model\\Table\\WindowsTable::class,\n\t\t\t'BarBars' => \\TestApp\\Model\\Table\\BarBarsTable::class,\n\t\t\t'BarBarsAbstract' => \\TestApp\\Model\\Table\\BarBarsAbstractTable::class,\n\t\t\t'Callbacks' => \\TestApp\\Model\\Table\\CallbacksTable::class,\n\t\t\t'Cars' => \\TestApp\\Model\\Table\\CarsTable::class,\n\t\t\t'Controllers.Houses' => \\Controllers\\Model\\Table\\HousesTable::class,\n\t\t\t'CustomFinder' => \\TestApp\\Model\\Table\\CustomFinderTable::class,\n\t\t\t'Exceptions' => \\TestApp\\Model\\Table\\ExceptionsTable::class,\n\t\t\t'Foos' => \\TestApp\\Model\\Table\\FoosTable::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class,\n\t\t\t'Relations.Bars' => \\Relations\\Model\\Table\\BarsTable::class,\n\t\t\t'Relations.Foos' => \\Relations\\Model\\Table\\FoosTable::class,\n\t\t\t'Relations.Users' => \\Relations\\Model\\Table\\UsersTable::class,\n\t\t\t'SkipMe' => \\TestApp\\Model\\Table\\SkipMeTable::class,\n\t\t\t'SkipSome' => \\TestApp\\Model\\Table\\SkipSomeTable::class,\n\t\t\t'Wheels' => \\TestApp\\Model\\Table\\WheelsTable::class,\n\t\t\t'WheelsExtra' => \\TestApp\\Model\\Table\\WheelsExtraTable::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Locator\\LocatorInterface::get(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Awesome\\Model\\Table\\HousesTable::class,\n\t\t\t'Awesome.Windows' => \\Awesome\\Model\\Table\\WindowsTable::class,\n\t\t\t'BarBars' => \\TestApp\\Model\\Table\\BarBarsTable::class,\n\t\t\t'BarBarsAbstract' => \\TestApp\\Model\\Table\\BarBarsAbstractTable::class,\n\t\t\t'Callbacks' => \\TestApp\\Model\\Table\\CallbacksTable::class,\n\t\t\t'Cars' => \\TestApp\\Model\\Table\\CarsTable::class,\n\t\t\t'Controllers.Houses' => \\Controllers\\Model\\Table\\HousesTable::class,\n\t\t\t'CustomFinder' => \\TestApp\\Model\\Table\\CustomFinderTable::class,\n\t\t\t'Exceptions' => \\TestApp\\Model\\Table\\ExceptionsTable::class,\n\t\t\t'Foos' => \\TestApp\\Model\\Table\\FoosTable::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class,\n\t\t\t'Relations.Bars' => \\Relations\\Model\\Table\\BarsTable::class,\n\t\t\t'Relations.Foos' => \\Relations\\Model\\Table\\FoosTable::class,\n\t\t\t'Relations.Users' => \\Relations\\Model\\Table\\UsersTable::class,\n\t\t\t'SkipMe' => \\TestApp\\Model\\Table\\SkipMeTable::class,\n\t\t\t'SkipSome' => \\TestApp\\Model\\Table\\SkipSomeTable::class,\n\t\t\t'Wheels' => \\TestApp\\Model\\Table\\WheelsTable::class,\n\t\t\t'WheelsExtra' => \\TestApp\\Model\\Table\\WheelsExtraTable::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::addBehavior(),\n\t\t0,\n\t\t'CounterCache',\n\t\t'MyNamespace/MyPlugin.My',\n\t\t'Shim.Nullable',\n\t\t'Timestamp',\n\t\t'Translate',\n\t\t'Tree',\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::belongToMany(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::belongsTo(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::find(0),\n\t\tmap([\n\t\t\t'all' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'children' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'list' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'path' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'somethingCustom' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'threaded' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'treeList' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::getBehavior(),\n\t\tmap([\n\t\t\t'CounterCache' => \\Cake\\ORM\\Behavior\\CounterCacheBehavior::class,\n\t\t\t'My' => \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior::class,\n\t\t\t'Nullable' => \\Shim\\Model\\Behavior\\NullableBehavior::class,\n\t\t\t'Timestamp' => \\Cake\\ORM\\Behavior\\TimestampBehavior::class,\n\t\t\t'Translate' => \\Cake\\ORM\\Behavior\\TranslateBehavior::class,\n\t\t\t'Tree' => \\Cake\\ORM\\Behavior\\TreeBehavior::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::hasBehavior(),\n\t\t0,\n\t\t'CounterCache',\n\t\t'My',\n\t\t'Nullable',\n\t\t'Timestamp',\n\t\t'Translate',\n\t\t'Tree',\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::hasMany(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::hasOne(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::removeBehavior(),\n\t\t0,\n\t\t'CounterCache',\n\t\t'My',\n\t\t'Nullable',\n\t\t'Timestamp',\n\t\t'Translate',\n\t\t'Tree',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Routing\\Router::pathUrl(),\n\t\t0,\n\t\targumentsSet('routePaths'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\TestSuite\\TestCase::addFixture(),\n\t\t0,\n\t\t'app.SmallWindows',\n\t\t'core.Posts',\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.MyNamespace/MyPlugin.Sub/My',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyArray(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyDate(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyDateTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyFile(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyFor(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyString(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyArray(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyDate(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyDateTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyFile(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyString(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::requirePresence(),\n\t\t1,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\toverride(\n\t\t\\Cake\\View\\CellTrait::cell(),\n\t\tmap([\n\t\t\t'Test' => \\TestApp\\View\\Cell\\TestCell::class,\n\t\t\t'Test::custom' => \\TestApp\\View\\Cell\\TestCell::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\Helper\\FormHelper::control(),\n\t\t0,\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'modified',\n\t\t'name',\n\t\t'params',\n\t\t'status',\n\t\t'user_id',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\Helper\\HtmlHelper::linkFromPath(),\n\t\t1,\n\t\targumentsSet('routePaths'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\Helper\\UrlHelper::buildFromPath(),\n\t\t0,\n\t\targumentsSet('routePaths'),\n\t);\n\n\toverride(\n\t\t\\Cake\\View\\View::addHelper(0),\n\t\tmap([\n\t\t\t'Breadcrumbs' => \\Cake\\View\\Helper\\BreadcrumbsHelper::class,\n\t\t\t'Flash' => \\Cake\\View\\Helper\\FlashHelper::class,\n\t\t\t'Form' => \\Cake\\View\\Helper\\FormHelper::class,\n\t\t\t'Html' => \\TestApp\\View\\Helper\\HtmlHelper::class,\n\t\t\t'IdeHelper.DocBlock' => \\IdeHelper\\View\\Helper\\DocBlockHelper::class,\n\t\t\t'My' => \\TestApp\\View\\Helper\\MyHelper::class,\n\t\t\t'MyMethod' => \\TestApp\\View\\Helper\\MyMethodHelper::class,\n\t\t\t'Number' => \\Cake\\View\\Helper\\NumberHelper::class,\n\t\t\t'Paginator' => \\Cake\\View\\Helper\\PaginatorHelper::class,\n\t\t\t'Shim.Configure' => \\Shim\\View\\Helper\\ConfigureHelper::class,\n\t\t\t'Shim.Cookie' => \\Shim\\View\\Helper\\CookieHelper::class,\n\t\t\t'Shim.Number' => \\Shim\\View\\Helper\\NumberHelper::class,\n\t\t\t'Text' => \\Cake\\View\\Helper\\TextHelper::class,\n\t\t\t'Time' => \\Cake\\View\\Helper\\TimeHelper::class,\n\t\t\t'Url' => \\Cake\\View\\Helper\\UrlHelper::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\View::element(),\n\t\t0,\n\t\t'Awesome.pagination',\n\t\t'deeply/nested',\n\t\t'example',\n\t);\n\n\toverride(\n\t\t\\Cake\\View\\View::loadHelper(0),\n\t\tmap([\n\t\t\t'Breadcrumbs' => \\Cake\\View\\Helper\\BreadcrumbsHelper::class,\n\t\t\t'Flash' => \\Cake\\View\\Helper\\FlashHelper::class,\n\t\t\t'Form' => \\Cake\\View\\Helper\\FormHelper::class,\n\t\t\t'Html' => \\TestApp\\View\\Helper\\HtmlHelper::class,\n\t\t\t'IdeHelper.DocBlock' => \\IdeHelper\\View\\Helper\\DocBlockHelper::class,\n\t\t\t'My' => \\TestApp\\View\\Helper\\MyHelper::class,\n\t\t\t'MyMethod' => \\TestApp\\View\\Helper\\MyMethodHelper::class,\n\t\t\t'Number' => \\Cake\\View\\Helper\\NumberHelper::class,\n\t\t\t'Paginator' => \\Cake\\View\\Helper\\PaginatorHelper::class,\n\t\t\t'Shim.Configure' => \\Shim\\View\\Helper\\ConfigureHelper::class,\n\t\t\t'Shim.Cookie' => \\Shim\\View\\Helper\\CookieHelper::class,\n\t\t\t'Shim.Number' => \\Shim\\View\\Helper\\NumberHelper::class,\n\t\t\t'Text' => \\Cake\\View\\Helper\\TextHelper::class,\n\t\t\t'Time' => \\Cake\\View\\Helper\\TimeHelper::class,\n\t\t\t'Url' => \\Cake\\View\\Helper\\UrlHelper::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\ViewBuilder::addHelper(),\n\t\t0,\n\t\t'Breadcrumbs',\n\t\t'Flash',\n\t\t'Form',\n\t\t'Html',\n\t\t'IdeHelper.DocBlock',\n\t\t'My',\n\t\t'MyMethod',\n\t\t'Number',\n\t\t'Paginator',\n\t\t'Shim.Configure',\n\t\t'Shim.Cookie',\n\t\t'Shim.Number',\n\t\t'Text',\n\t\t'Time',\n\t\t'Url',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\ViewBuilder::setLayout(),\n\t\t0,\n\t\t'ajax',\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseMigration::hasTable(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseMigration::table(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseSeed::hasTable(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseSeed::table(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::addColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::addColumn(),\n\t\t1,\n\t\targumentsSet('columnTypes'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::changeColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::changeColumn(),\n\t\t1,\n\t\targumentsSet('columnTypes'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::hasColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::removeColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::renameColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::renameColumn(),\n\t\t1,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\__d(),\n\t\t0,\n\t\t'awesome',\n\t\t'cake',\n\t\t'controllers',\n\t\t'ide_helper',\n\t\t'my_namespace/my_plugin',\n\t\t'relations',\n\t\t'shim',\n\t);\n\n\texpectedArguments(\n\t\t\\env(),\n\t\t0,\n\t\t'HTTP_HOST',\n\t);\n\n\texpectedArguments(\n\t\t\\urlArray(),\n\t\t0,\n\t\targumentsSet('routePaths'),\n\t);\n\n\tregisterArgumentsSet(\n\t\t'cacheEngines',\n\t\t'_cake_model_',\n\t\t'_cake_translations_',\n\t\t'default',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'columnNames',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'columnTypes',\n\t\t'biginteger',\n\t\t'binary',\n\t\t'binaryuuid',\n\t\t'bit',\n\t\t'blob',\n\t\t'boolean',\n\t\t'char',\n\t\t'date',\n\t\t'datetime',\n\t\t'decimal',\n\t\t'double',\n\t\t'float',\n\t\t'integer',\n\t\t'json',\n\t\t'smallinteger',\n\t\t'string',\n\t\t'text',\n\t\t'time',\n\t\t'timestamp',\n\t\t'uuid',\n\t\t'year',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'configureKeys',\n\t\t'App',\n\t\t'App.encoding',\n\t\t'App.namespace',\n\t\t'App.paths',\n\t\t'App.paths.templates',\n\t\t'IdeHelper',\n\t\t'IdeHelper.skipDatabaseTables',\n\t\t'Shim',\n\t\t'Shim.deprecations',\n\t\t'debug',\n\t\t'plugins',\n\t\t'plugins.Bake',\n\t\t'plugins.Cake/TwigView',\n\t\t'plugins.Migrations',\n\t\t'plugins.Shim',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Cake\\ORM\\Entity',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Relations\\Model\\Entity\\Bar',\n\t\t'id',\n\t\t'name',\n\t\t'user',\n\t\t'user_id',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Relations\\Model\\Entity\\Foo',\n\t\t'id',\n\t\t'name',\n\t\t'params',\n\t\t'user',\n\t\t'user_id',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Relations\\Model\\Entity\\User',\n\t\t'bar',\n\t\t'foo',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\BarBar',\n\t\t'content',\n\t\t'created',\n\t\t'foo',\n\t\t'houses',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\BarBarsAbstract',\n\t\t'content',\n\t\t'created',\n\t\t'foo',\n\t\t'houses',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\Car',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'modified',\n\t\t'name',\n\t\t'status',\n\t\t'wheels',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\Foo',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'name',\n\t\t'params',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\Wheel',\n\t\t'car',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'name',\n\t\t'virtual_one',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'routePaths',\n\t\t'Awesome.Admin/AwesomeHouses::openDoor',\n\t\t'Bar::index',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'tableNames',\n\t\t'wheels',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'validationWhen',\n\t\t'create',\n\t\t'update',\n\t);\n\n}\n"
  },
  {
    "path": "tests/test_files/meta/phpstorm/.meta_52.php",
    "content": "<?php\n// @link https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata\nnamespace PHPSTORM_META {\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::add(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::clear(),\n\t\t0,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::clearGroup(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::decrement(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::delete(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::deleteMany(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::increment(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::read(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::readMany(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::remember(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::write(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texitPoint(\\Cake\\Console\\ConsoleIo::abort());\n\n\toverride(\n\t\t\\Cake\\Console\\ConsoleIo::helper(0),\n\t\tmap([\n\t\t\t'Banner' => \\Cake\\Command\\Helper\\BannerHelper::class,\n\t\t\t'Progress' => \\Cake\\Command\\Helper\\ProgressHelper::class,\n\t\t\t'Table' => \\Cake\\Command\\Helper\\TableHelper::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Controller\\ComponentRegistry::unload(),\n\t\t0,\n\t\t'CheckHttpCache',\n\t\t'Flash',\n\t\t'FormProtection',\n\t\t'My',\n\t\t'MyController',\n\t\t'MyOther',\n\t\t'RequestHandler',\n\t);\n\n\toverride(\n\t\t\\Cake\\Controller\\Controller::loadComponent(0),\n\t\tmap([\n\t\t\t'CheckHttpCache' => \\TestApp\\Controller\\Component\\CheckHttpCacheComponent::class,\n\t\t\t'Flash' => \\Cake\\Controller\\Component\\FlashComponent::class,\n\t\t\t'FormProtection' => \\Cake\\Controller\\Component\\FormProtectionComponent::class,\n\t\t\t'My' => \\TestApp\\Controller\\Component\\MyComponent::class,\n\t\t\t'MyController' => \\TestApp\\Controller\\Component\\MyControllerComponent::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Controller\\Component\\MyComponent::class,\n\t\t\t'MyOther' => \\TestApp\\Controller\\Component\\MyOtherComponent::class,\n\t\t\t'Shim.RequestHandler' => \\Shim\\Controller\\Component\\RequestHandlerComponent::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::check(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::consume(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::consumeOrFail(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::delete(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::read(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::readOrFail(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::write(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\toverride(\n\t\t\\Cake\\Core\\PluginApplicationInterface::addPlugin(0),\n\t\tmap([\n\t\t\t'Bake' => \\Cake\\Http\\BaseApplication::class,\n\t\t\t'Cake/TwigView' => \\Cake\\Http\\BaseApplication::class,\n\t\t\t'Migrations' => \\Cake\\Http\\BaseApplication::class,\n\t\t\t'Shim' => \\Cake\\Http\\BaseApplication::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\Database\\TypeFactory::build(0),\n\t\tmap([\n\t\t\t'biginteger' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'binary' => \\Cake\\Database\\Type\\BinaryType::class,\n\t\t\t'binaryuuid' => \\Cake\\Database\\Type\\BinaryUuidType::class,\n\t\t\t'boolean' => \\Cake\\Database\\Type\\BoolType::class,\n\t\t\t'char' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'date' => \\Cake\\Database\\Type\\DateType::class,\n\t\t\t'datetime' => \\Cake\\Database\\Type\\DateTimeType::class,\n\t\t\t'datetimefractional' => \\Cake\\Database\\Type\\DateTimeFractionalType::class,\n\t\t\t'decimal' => \\Cake\\Database\\Type\\DecimalType::class,\n\t\t\t'float' => \\Cake\\Database\\Type\\FloatType::class,\n\t\t\t'geometry' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'integer' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'json' => \\Cake\\Database\\Type\\JsonType::class,\n\t\t\t'linestring' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'nativeuuid' => \\Cake\\Database\\Type\\UuidType::class,\n\t\t\t'point' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'polygon' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'smallinteger' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'string' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'text' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'time' => \\Cake\\Database\\Type\\TimeType::class,\n\t\t\t'timestamp' => \\Cake\\Database\\Type\\DateTimeType::class,\n\t\t\t'timestampfractional' => \\Cake\\Database\\Type\\DateTimeFractionalType::class,\n\t\t\t'timestamptimezone' => \\Cake\\Database\\Type\\DateTimeTimezoneType::class,\n\t\t\t'tinyinteger' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'uuid' => \\Cake\\Database\\Type\\UuidType::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Database\\TypeFactory::map(),\n\t\t0,\n\t\t'biginteger',\n\t\t'binary',\n\t\t'binaryuuid',\n\t\t'boolean',\n\t\t'char',\n\t\t'date',\n\t\t'datetime',\n\t\t'datetimefractional',\n\t\t'decimal',\n\t\t'float',\n\t\t'geometry',\n\t\t'integer',\n\t\t'json',\n\t\t'linestring',\n\t\t'nativeuuid',\n\t\t'point',\n\t\t'polygon',\n\t\t'smallinteger',\n\t\t'string',\n\t\t'text',\n\t\t'time',\n\t\t'timestamp',\n\t\t'timestampfractional',\n\t\t'timestamptimezone',\n\t\t'tinyinteger',\n\t\t'uuid',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Datasource\\ConnectionManager::get(),\n\t\t0,\n\t\t'test',\n\t);\n\n\toverride(\n\t\t\\Cake\\Datasource\\ModelAwareTrait::fetchModel(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Awesome\\Model\\Table\\HousesTable::class,\n\t\t\t'Awesome.Windows' => \\Awesome\\Model\\Table\\WindowsTable::class,\n\t\t\t'BarBars' => \\TestApp\\Model\\Table\\BarBarsTable::class,\n\t\t\t'BarBarsAbstract' => \\TestApp\\Model\\Table\\BarBarsAbstractTable::class,\n\t\t\t'Callbacks' => \\TestApp\\Model\\Table\\CallbacksTable::class,\n\t\t\t'Cars' => \\TestApp\\Model\\Table\\CarsTable::class,\n\t\t\t'Controllers.Houses' => \\Controllers\\Model\\Table\\HousesTable::class,\n\t\t\t'CustomFinder' => \\TestApp\\Model\\Table\\CustomFinderTable::class,\n\t\t\t'Exceptions' => \\TestApp\\Model\\Table\\ExceptionsTable::class,\n\t\t\t'Foos' => \\TestApp\\Model\\Table\\FoosTable::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class,\n\t\t\t'Relations.Bars' => \\Relations\\Model\\Table\\BarsTable::class,\n\t\t\t'Relations.Foos' => \\Relations\\Model\\Table\\FoosTable::class,\n\t\t\t'Relations.Users' => \\Relations\\Model\\Table\\UsersTable::class,\n\t\t\t'SkipMe' => \\TestApp\\Model\\Table\\SkipMeTable::class,\n\t\t\t'SkipSome' => \\TestApp\\Model\\Table\\SkipSomeTable::class,\n\t\t\t'Wheels' => \\TestApp\\Model\\Table\\WheelsTable::class,\n\t\t\t'WheelsExtra' => \\TestApp\\Model\\Table\\WheelsExtraTable::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\Datasource\\QueryInterface::find(0),\n\t\tmap([\n\t\t\t'all' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'children' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'list' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'path' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'somethingCustom' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'threaded' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'treeList' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\Http\\ServerRequest::getAttribute(0),\n\t\tmap([\n\t\t\t'base' => 'string',\n\t\t\t'cspScriptNonce' => 'string',\n\t\t\t'cspStyleNonce' => 'string',\n\t\t\t'csrfToken' => 'string',\n\t\t\t'formTokenData' => 'array',\n\t\t\t'here' => 'string',\n\t\t\t'paging' => 'array',\n\t\t\t'params' => 'array',\n\t\t\t'route' => \\Cake\\Routing\\Route\\Route::class,\n\t\t\t'session' => \\Cake\\Http\\Session::class,\n\t\t\t'webroot' => 'string',\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Http\\ServerRequest::getParam(),\n\t\t0,\n\t\t'_ext',\n\t\t'_matchedRoute',\n\t\t'action',\n\t\t'controller',\n\t\t'pass',\n\t\t'plugin',\n\t\t'prefix',\n\t);\n\n\toverride(\n\t\t\\Cake\\Mailer\\MailerAwareTrait::getMailer(0),\n\t\tmap([\n\t\t\t'User' => \\TestApp\\Mailer\\UserMailer::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Association::find(0),\n\t\tmap([\n\t\t\t'all' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'children' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'list' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'path' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'somethingCustom' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'threaded' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'treeList' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Locator\\LocatorAwareTrait::fetchTable(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Awesome\\Model\\Table\\HousesTable::class,\n\t\t\t'Awesome.Windows' => \\Awesome\\Model\\Table\\WindowsTable::class,\n\t\t\t'BarBars' => \\TestApp\\Model\\Table\\BarBarsTable::class,\n\t\t\t'BarBarsAbstract' => \\TestApp\\Model\\Table\\BarBarsAbstractTable::class,\n\t\t\t'Callbacks' => \\TestApp\\Model\\Table\\CallbacksTable::class,\n\t\t\t'Cars' => \\TestApp\\Model\\Table\\CarsTable::class,\n\t\t\t'Controllers.Houses' => \\Controllers\\Model\\Table\\HousesTable::class,\n\t\t\t'CustomFinder' => \\TestApp\\Model\\Table\\CustomFinderTable::class,\n\t\t\t'Exceptions' => \\TestApp\\Model\\Table\\ExceptionsTable::class,\n\t\t\t'Foos' => \\TestApp\\Model\\Table\\FoosTable::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class,\n\t\t\t'Relations.Bars' => \\Relations\\Model\\Table\\BarsTable::class,\n\t\t\t'Relations.Foos' => \\Relations\\Model\\Table\\FoosTable::class,\n\t\t\t'Relations.Users' => \\Relations\\Model\\Table\\UsersTable::class,\n\t\t\t'SkipMe' => \\TestApp\\Model\\Table\\SkipMeTable::class,\n\t\t\t'SkipSome' => \\TestApp\\Model\\Table\\SkipSomeTable::class,\n\t\t\t'Wheels' => \\TestApp\\Model\\Table\\WheelsTable::class,\n\t\t\t'WheelsExtra' => \\TestApp\\Model\\Table\\WheelsExtraTable::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Locator\\LocatorInterface::get(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Awesome\\Model\\Table\\HousesTable::class,\n\t\t\t'Awesome.Windows' => \\Awesome\\Model\\Table\\WindowsTable::class,\n\t\t\t'BarBars' => \\TestApp\\Model\\Table\\BarBarsTable::class,\n\t\t\t'BarBarsAbstract' => \\TestApp\\Model\\Table\\BarBarsAbstractTable::class,\n\t\t\t'Callbacks' => \\TestApp\\Model\\Table\\CallbacksTable::class,\n\t\t\t'Cars' => \\TestApp\\Model\\Table\\CarsTable::class,\n\t\t\t'Controllers.Houses' => \\Controllers\\Model\\Table\\HousesTable::class,\n\t\t\t'CustomFinder' => \\TestApp\\Model\\Table\\CustomFinderTable::class,\n\t\t\t'Exceptions' => \\TestApp\\Model\\Table\\ExceptionsTable::class,\n\t\t\t'Foos' => \\TestApp\\Model\\Table\\FoosTable::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class,\n\t\t\t'Relations.Bars' => \\Relations\\Model\\Table\\BarsTable::class,\n\t\t\t'Relations.Foos' => \\Relations\\Model\\Table\\FoosTable::class,\n\t\t\t'Relations.Users' => \\Relations\\Model\\Table\\UsersTable::class,\n\t\t\t'SkipMe' => \\TestApp\\Model\\Table\\SkipMeTable::class,\n\t\t\t'SkipSome' => \\TestApp\\Model\\Table\\SkipSomeTable::class,\n\t\t\t'Wheels' => \\TestApp\\Model\\Table\\WheelsTable::class,\n\t\t\t'WheelsExtra' => \\TestApp\\Model\\Table\\WheelsExtraTable::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::addBehavior(),\n\t\t0,\n\t\t'CounterCache',\n\t\t'MyNamespace/MyPlugin.My',\n\t\t'Shim.Nullable',\n\t\t'Timestamp',\n\t\t'Translate',\n\t\t'Tree',\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::belongToMany(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::belongsTo(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::find(0),\n\t\tmap([\n\t\t\t'all' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'children' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'list' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'path' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'somethingCustom' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'threaded' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'treeList' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::getBehavior(),\n\t\tmap([\n\t\t\t'CounterCache' => \\Cake\\ORM\\Behavior\\CounterCacheBehavior::class,\n\t\t\t'My' => \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior::class,\n\t\t\t'Nullable' => \\Shim\\Model\\Behavior\\NullableBehavior::class,\n\t\t\t'Timestamp' => \\Cake\\ORM\\Behavior\\TimestampBehavior::class,\n\t\t\t'Translate' => \\Cake\\ORM\\Behavior\\TranslateBehavior::class,\n\t\t\t'Tree' => \\Cake\\ORM\\Behavior\\TreeBehavior::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::hasBehavior(),\n\t\t0,\n\t\t'CounterCache',\n\t\t'My',\n\t\t'Nullable',\n\t\t'Timestamp',\n\t\t'Translate',\n\t\t'Tree',\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::hasMany(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::hasOne(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::removeBehavior(),\n\t\t0,\n\t\t'CounterCache',\n\t\t'My',\n\t\t'Nullable',\n\t\t'Timestamp',\n\t\t'Translate',\n\t\t'Tree',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Routing\\Router::pathUrl(),\n\t\t0,\n\t\targumentsSet('routePaths'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\TestSuite\\TestCase::addFixture(),\n\t\t0,\n\t\t'app.SmallWindows',\n\t\t'core.Posts',\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.MyNamespace/MyPlugin.Sub/My',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyArray(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyDate(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyDateTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyFile(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyFor(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyString(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyArray(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyDate(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyDateTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyFile(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyString(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::requirePresence(),\n\t\t1,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\toverride(\n\t\t\\Cake\\View\\CellTrait::cell(),\n\t\tmap([\n\t\t\t'Test' => \\TestApp\\View\\Cell\\TestCell::class,\n\t\t\t'Test::custom' => \\TestApp\\View\\Cell\\TestCell::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\Helper\\FormHelper::control(),\n\t\t0,\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'modified',\n\t\t'name',\n\t\t'params',\n\t\t'status',\n\t\t'user_id',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\Helper\\HtmlHelper::linkFromPath(),\n\t\t1,\n\t\targumentsSet('routePaths'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\Helper\\UrlHelper::buildFromPath(),\n\t\t0,\n\t\targumentsSet('routePaths'),\n\t);\n\n\toverride(\n\t\t\\Cake\\View\\View::addHelper(0),\n\t\tmap([\n\t\t\t'Breadcrumbs' => \\Cake\\View\\Helper\\BreadcrumbsHelper::class,\n\t\t\t'Flash' => \\Cake\\View\\Helper\\FlashHelper::class,\n\t\t\t'Form' => \\Cake\\View\\Helper\\FormHelper::class,\n\t\t\t'Html' => \\TestApp\\View\\Helper\\HtmlHelper::class,\n\t\t\t'IdeHelper.DocBlock' => \\IdeHelper\\View\\Helper\\DocBlockHelper::class,\n\t\t\t'My' => \\TestApp\\View\\Helper\\MyHelper::class,\n\t\t\t'MyMethod' => \\TestApp\\View\\Helper\\MyMethodHelper::class,\n\t\t\t'Number' => \\Cake\\View\\Helper\\NumberHelper::class,\n\t\t\t'Paginator' => \\Cake\\View\\Helper\\PaginatorHelper::class,\n\t\t\t'Shim.Configure' => \\Shim\\View\\Helper\\ConfigureHelper::class,\n\t\t\t'Shim.Cookie' => \\Shim\\View\\Helper\\CookieHelper::class,\n\t\t\t'Shim.Number' => \\Shim\\View\\Helper\\NumberHelper::class,\n\t\t\t'Text' => \\Cake\\View\\Helper\\TextHelper::class,\n\t\t\t'Time' => \\Cake\\View\\Helper\\TimeHelper::class,\n\t\t\t'Url' => \\Cake\\View\\Helper\\UrlHelper::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\View::element(),\n\t\t0,\n\t\t'Awesome.pagination',\n\t\t'deeply/nested',\n\t\t'example',\n\t);\n\n\toverride(\n\t\t\\Cake\\View\\View::loadHelper(0),\n\t\tmap([\n\t\t\t'Breadcrumbs' => \\Cake\\View\\Helper\\BreadcrumbsHelper::class,\n\t\t\t'Flash' => \\Cake\\View\\Helper\\FlashHelper::class,\n\t\t\t'Form' => \\Cake\\View\\Helper\\FormHelper::class,\n\t\t\t'Html' => \\TestApp\\View\\Helper\\HtmlHelper::class,\n\t\t\t'IdeHelper.DocBlock' => \\IdeHelper\\View\\Helper\\DocBlockHelper::class,\n\t\t\t'My' => \\TestApp\\View\\Helper\\MyHelper::class,\n\t\t\t'MyMethod' => \\TestApp\\View\\Helper\\MyMethodHelper::class,\n\t\t\t'Number' => \\Cake\\View\\Helper\\NumberHelper::class,\n\t\t\t'Paginator' => \\Cake\\View\\Helper\\PaginatorHelper::class,\n\t\t\t'Shim.Configure' => \\Shim\\View\\Helper\\ConfigureHelper::class,\n\t\t\t'Shim.Cookie' => \\Shim\\View\\Helper\\CookieHelper::class,\n\t\t\t'Shim.Number' => \\Shim\\View\\Helper\\NumberHelper::class,\n\t\t\t'Text' => \\Cake\\View\\Helper\\TextHelper::class,\n\t\t\t'Time' => \\Cake\\View\\Helper\\TimeHelper::class,\n\t\t\t'Url' => \\Cake\\View\\Helper\\UrlHelper::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\ViewBuilder::addHelper(),\n\t\t0,\n\t\t'Breadcrumbs',\n\t\t'Flash',\n\t\t'Form',\n\t\t'Html',\n\t\t'IdeHelper.DocBlock',\n\t\t'My',\n\t\t'MyMethod',\n\t\t'Number',\n\t\t'Paginator',\n\t\t'Shim.Configure',\n\t\t'Shim.Cookie',\n\t\t'Shim.Number',\n\t\t'Text',\n\t\t'Time',\n\t\t'Url',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\ViewBuilder::setLayout(),\n\t\t0,\n\t\t'ajax',\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseMigration::hasTable(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseMigration::table(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseSeed::hasTable(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseSeed::table(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::addColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::addColumn(),\n\t\t1,\n\t\targumentsSet('columnTypes'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::changeColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::changeColumn(),\n\t\t1,\n\t\targumentsSet('columnTypes'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::hasColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::removeColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::renameColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::renameColumn(),\n\t\t1,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\__d(),\n\t\t0,\n\t\t'awesome',\n\t\t'cake',\n\t\t'controllers',\n\t\t'ide_helper',\n\t\t'my_namespace/my_plugin',\n\t\t'relations',\n\t\t'shim',\n\t);\n\n\texpectedArguments(\n\t\t\\env(),\n\t\t0,\n\t\t'HTTP_HOST',\n\t);\n\n\texpectedArguments(\n\t\t\\urlArray(),\n\t\t0,\n\t\targumentsSet('routePaths'),\n\t);\n\n\tregisterArgumentsSet(\n\t\t'cacheEngines',\n\t\t'_cake_model_',\n\t\t'_cake_translations_',\n\t\t'default',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'columnNames',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'columnTypes',\n\t\t'biginteger',\n\t\t'binary',\n\t\t'binaryuuid',\n\t\t'bit',\n\t\t'blob',\n\t\t'boolean',\n\t\t'char',\n\t\t'date',\n\t\t'datetime',\n\t\t'decimal',\n\t\t'double',\n\t\t'float',\n\t\t'integer',\n\t\t'json',\n\t\t'smallinteger',\n\t\t'string',\n\t\t'text',\n\t\t'time',\n\t\t'timestamp',\n\t\t'uuid',\n\t\t'year',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'configureKeys',\n\t\t'App',\n\t\t'App.encoding',\n\t\t'App.namespace',\n\t\t'App.paths',\n\t\t'App.paths.templates',\n\t\t'IdeHelper',\n\t\t'IdeHelper.skipDatabaseTables',\n\t\t'Shim',\n\t\t'Shim.deprecations',\n\t\t'debug',\n\t\t'plugins',\n\t\t'plugins.Bake',\n\t\t'plugins.Cake/TwigView',\n\t\t'plugins.Migrations',\n\t\t'plugins.Shim',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Cake\\ORM\\Entity',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Relations\\Model\\Entity\\Bar',\n\t\t'id',\n\t\t'name',\n\t\t'user',\n\t\t'user_id',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Relations\\Model\\Entity\\Foo',\n\t\t'id',\n\t\t'name',\n\t\t'params',\n\t\t'user',\n\t\t'user_id',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Relations\\Model\\Entity\\User',\n\t\t'bar',\n\t\t'foo',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\BarBar',\n\t\t'content',\n\t\t'created',\n\t\t'foo',\n\t\t'houses',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\BarBarsAbstract',\n\t\t'content',\n\t\t'created',\n\t\t'foo',\n\t\t'houses',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\Car',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'modified',\n\t\t'name',\n\t\t'status',\n\t\t'wheels',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\Foo',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'name',\n\t\t'params',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\Wheel',\n\t\t'car',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'name',\n\t\t'virtual_one',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'routePaths',\n\t\t'Awesome.Admin/AwesomeHouses::openDoor',\n\t\t'Bar::index',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'tableNames',\n\t\t'wheels',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'validationWhen',\n\t\t'create',\n\t\t'update',\n\t);\n\n}\n"
  },
  {
    "path": "tests/test_files/meta/phpstorm/.meta_lowest.php",
    "content": "<?php\n// @link https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata\nnamespace PHPSTORM_META {\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::add(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::clear(),\n\t\t0,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::clearGroup(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::decrement(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::delete(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::deleteMany(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::increment(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::read(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::readMany(),\n\t\t1,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::remember(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Cache\\Cache::write(),\n\t\t2,\n\t\targumentsSet('cacheEngines'),\n\t);\n\n\texitPoint(\\Cake\\Console\\ConsoleIo::abort());\n\n\toverride(\n\t\t\\Cake\\Console\\ConsoleIo::helper(0),\n\t\tmap([\n\t\t\t'Banner' => \\Cake\\Command\\Helper\\BannerHelper::class,\n\t\t\t'Progress' => \\Cake\\Command\\Helper\\ProgressHelper::class,\n\t\t\t'Table' => \\Cake\\Command\\Helper\\TableHelper::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Controller\\ComponentRegistry::unload(),\n\t\t0,\n\t\t'CheckHttpCache',\n\t\t'Flash',\n\t\t'FormProtection',\n\t\t'My',\n\t\t'MyController',\n\t\t'MyOther',\n\t\t'RequestHandler',\n\t);\n\n\toverride(\n\t\t\\Cake\\Controller\\Controller::loadComponent(0),\n\t\tmap([\n\t\t\t'CheckHttpCache' => \\TestApp\\Controller\\Component\\CheckHttpCacheComponent::class,\n\t\t\t'Flash' => \\Cake\\Controller\\Component\\FlashComponent::class,\n\t\t\t'FormProtection' => \\Cake\\Controller\\Component\\FormProtectionComponent::class,\n\t\t\t'My' => \\TestApp\\Controller\\Component\\MyComponent::class,\n\t\t\t'MyController' => \\TestApp\\Controller\\Component\\MyControllerComponent::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Controller\\Component\\MyComponent::class,\n\t\t\t'MyOther' => \\TestApp\\Controller\\Component\\MyOtherComponent::class,\n\t\t\t'Shim.RequestHandler' => \\Shim\\Controller\\Component\\RequestHandlerComponent::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::check(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::consume(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::consumeOrFail(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::delete(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::read(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::readOrFail(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Core\\Configure::write(),\n\t\t0,\n\t\targumentsSet('configureKeys'),\n\t);\n\n\toverride(\n\t\t\\Cake\\Core\\PluginApplicationInterface::addPlugin(0),\n\t\tmap([\n\t\t\t'Bake' => \\Cake\\Http\\BaseApplication::class,\n\t\t\t'Cake/TwigView' => \\Cake\\Http\\BaseApplication::class,\n\t\t\t'Migrations' => \\Cake\\Http\\BaseApplication::class,\n\t\t\t'Shim' => \\Cake\\Http\\BaseApplication::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\Database\\TypeFactory::build(0),\n\t\tmap([\n\t\t\t'biginteger' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'binary' => \\Cake\\Database\\Type\\BinaryType::class,\n\t\t\t'binaryuuid' => \\Cake\\Database\\Type\\BinaryUuidType::class,\n\t\t\t'boolean' => \\Cake\\Database\\Type\\BoolType::class,\n\t\t\t'char' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'date' => \\Cake\\Database\\Type\\DateType::class,\n\t\t\t'datetime' => \\Cake\\Database\\Type\\DateTimeType::class,\n\t\t\t'datetimefractional' => \\Cake\\Database\\Type\\DateTimeFractionalType::class,\n\t\t\t'decimal' => \\Cake\\Database\\Type\\DecimalType::class,\n\t\t\t'float' => \\Cake\\Database\\Type\\FloatType::class,\n\t\t\t'geometry' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'integer' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'json' => \\Cake\\Database\\Type\\JsonType::class,\n\t\t\t'linestring' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'point' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'polygon' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'smallinteger' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'string' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'text' => \\Cake\\Database\\Type\\StringType::class,\n\t\t\t'time' => \\Cake\\Database\\Type\\TimeType::class,\n\t\t\t'timestamp' => \\Cake\\Database\\Type\\DateTimeType::class,\n\t\t\t'timestampfractional' => \\Cake\\Database\\Type\\DateTimeFractionalType::class,\n\t\t\t'timestamptimezone' => \\Cake\\Database\\Type\\DateTimeTimezoneType::class,\n\t\t\t'tinyinteger' => \\Cake\\Database\\Type\\IntegerType::class,\n\t\t\t'uuid' => \\Cake\\Database\\Type\\UuidType::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Database\\TypeFactory::map(),\n\t\t0,\n\t\t'biginteger',\n\t\t'binary',\n\t\t'binaryuuid',\n\t\t'boolean',\n\t\t'char',\n\t\t'date',\n\t\t'datetime',\n\t\t'datetimefractional',\n\t\t'decimal',\n\t\t'float',\n\t\t'geometry',\n\t\t'integer',\n\t\t'json',\n\t\t'linestring',\n\t\t'point',\n\t\t'polygon',\n\t\t'smallinteger',\n\t\t'string',\n\t\t'text',\n\t\t'time',\n\t\t'timestamp',\n\t\t'timestampfractional',\n\t\t'timestamptimezone',\n\t\t'tinyinteger',\n\t\t'uuid',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Datasource\\ConnectionManager::get(),\n\t\t0,\n\t\t'test',\n\t);\n\n\toverride(\n\t\t\\Cake\\Datasource\\ModelAwareTrait::fetchModel(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Awesome\\Model\\Table\\HousesTable::class,\n\t\t\t'Awesome.Windows' => \\Awesome\\Model\\Table\\WindowsTable::class,\n\t\t\t'BarBars' => \\TestApp\\Model\\Table\\BarBarsTable::class,\n\t\t\t'BarBarsAbstract' => \\TestApp\\Model\\Table\\BarBarsAbstractTable::class,\n\t\t\t'Callbacks' => \\TestApp\\Model\\Table\\CallbacksTable::class,\n\t\t\t'Cars' => \\TestApp\\Model\\Table\\CarsTable::class,\n\t\t\t'Controllers.Houses' => \\Controllers\\Model\\Table\\HousesTable::class,\n\t\t\t'CustomFinder' => \\TestApp\\Model\\Table\\CustomFinderTable::class,\n\t\t\t'Exceptions' => \\TestApp\\Model\\Table\\ExceptionsTable::class,\n\t\t\t'Foos' => \\TestApp\\Model\\Table\\FoosTable::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class,\n\t\t\t'Relations.Bars' => \\Relations\\Model\\Table\\BarsTable::class,\n\t\t\t'Relations.Foos' => \\Relations\\Model\\Table\\FoosTable::class,\n\t\t\t'Relations.Users' => \\Relations\\Model\\Table\\UsersTable::class,\n\t\t\t'SkipMe' => \\TestApp\\Model\\Table\\SkipMeTable::class,\n\t\t\t'SkipSome' => \\TestApp\\Model\\Table\\SkipSomeTable::class,\n\t\t\t'Wheels' => \\TestApp\\Model\\Table\\WheelsTable::class,\n\t\t\t'WheelsExtra' => \\TestApp\\Model\\Table\\WheelsExtraTable::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\Datasource\\QueryInterface::find(0),\n\t\tmap([\n\t\t\t'all' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'children' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'list' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'path' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'somethingCustom' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'threaded' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'treeList' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\Http\\ServerRequest::getAttribute(0),\n\t\tmap([\n\t\t\t'base' => 'string',\n\t\t\t'cspScriptNonce' => 'string',\n\t\t\t'cspStyleNonce' => 'string',\n\t\t\t'csrfToken' => 'string',\n\t\t\t'formTokenData' => 'array',\n\t\t\t'here' => 'string',\n\t\t\t'paging' => 'array',\n\t\t\t'params' => 'array',\n\t\t\t'route' => \\Cake\\Routing\\Route\\Route::class,\n\t\t\t'session' => \\Cake\\Http\\Session::class,\n\t\t\t'webroot' => 'string',\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Http\\ServerRequest::getParam(),\n\t\t0,\n\t\t'_ext',\n\t\t'_matchedRoute',\n\t\t'action',\n\t\t'controller',\n\t\t'pass',\n\t\t'plugin',\n\t\t'prefix',\n\t);\n\n\toverride(\n\t\t\\Cake\\Mailer\\MailerAwareTrait::getMailer(0),\n\t\tmap([\n\t\t\t'User' => \\TestApp\\Mailer\\UserMailer::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Association::find(0),\n\t\tmap([\n\t\t\t'all' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'children' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'list' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'path' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'somethingCustom' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'threaded' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'treeList' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Entity::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Cake\\ORM\\Entity'),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Locator\\LocatorAwareTrait::fetchTable(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Awesome\\Model\\Table\\HousesTable::class,\n\t\t\t'Awesome.Windows' => \\Awesome\\Model\\Table\\WindowsTable::class,\n\t\t\t'BarBars' => \\TestApp\\Model\\Table\\BarBarsTable::class,\n\t\t\t'BarBarsAbstract' => \\TestApp\\Model\\Table\\BarBarsAbstractTable::class,\n\t\t\t'Callbacks' => \\TestApp\\Model\\Table\\CallbacksTable::class,\n\t\t\t'Cars' => \\TestApp\\Model\\Table\\CarsTable::class,\n\t\t\t'Controllers.Houses' => \\Controllers\\Model\\Table\\HousesTable::class,\n\t\t\t'CustomFinder' => \\TestApp\\Model\\Table\\CustomFinderTable::class,\n\t\t\t'Exceptions' => \\TestApp\\Model\\Table\\ExceptionsTable::class,\n\t\t\t'Foos' => \\TestApp\\Model\\Table\\FoosTable::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class,\n\t\t\t'Relations.Bars' => \\Relations\\Model\\Table\\BarsTable::class,\n\t\t\t'Relations.Foos' => \\Relations\\Model\\Table\\FoosTable::class,\n\t\t\t'Relations.Users' => \\Relations\\Model\\Table\\UsersTable::class,\n\t\t\t'SkipMe' => \\TestApp\\Model\\Table\\SkipMeTable::class,\n\t\t\t'SkipSome' => \\TestApp\\Model\\Table\\SkipSomeTable::class,\n\t\t\t'Wheels' => \\TestApp\\Model\\Table\\WheelsTable::class,\n\t\t\t'WheelsExtra' => \\TestApp\\Model\\Table\\WheelsExtraTable::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Locator\\LocatorInterface::get(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Awesome\\Model\\Table\\HousesTable::class,\n\t\t\t'Awesome.Windows' => \\Awesome\\Model\\Table\\WindowsTable::class,\n\t\t\t'BarBars' => \\TestApp\\Model\\Table\\BarBarsTable::class,\n\t\t\t'BarBarsAbstract' => \\TestApp\\Model\\Table\\BarBarsAbstractTable::class,\n\t\t\t'Callbacks' => \\TestApp\\Model\\Table\\CallbacksTable::class,\n\t\t\t'Cars' => \\TestApp\\Model\\Table\\CarsTable::class,\n\t\t\t'Controllers.Houses' => \\Controllers\\Model\\Table\\HousesTable::class,\n\t\t\t'CustomFinder' => \\TestApp\\Model\\Table\\CustomFinderTable::class,\n\t\t\t'Exceptions' => \\TestApp\\Model\\Table\\ExceptionsTable::class,\n\t\t\t'Foos' => \\TestApp\\Model\\Table\\FoosTable::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\MyNamespace\\MyPlugin\\Model\\Table\\MyTable::class,\n\t\t\t'Relations.Bars' => \\Relations\\Model\\Table\\BarsTable::class,\n\t\t\t'Relations.Foos' => \\Relations\\Model\\Table\\FoosTable::class,\n\t\t\t'Relations.Users' => \\Relations\\Model\\Table\\UsersTable::class,\n\t\t\t'SkipMe' => \\TestApp\\Model\\Table\\SkipMeTable::class,\n\t\t\t'SkipSome' => \\TestApp\\Model\\Table\\SkipSomeTable::class,\n\t\t\t'Wheels' => \\TestApp\\Model\\Table\\WheelsTable::class,\n\t\t\t'WheelsExtra' => \\TestApp\\Model\\Table\\WheelsExtraTable::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::addBehavior(),\n\t\t0,\n\t\t'CounterCache',\n\t\t'MyNamespace/MyPlugin.My',\n\t\t'Shim.Nullable',\n\t\t'Timestamp',\n\t\t'Translate',\n\t\t'Tree',\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::belongToMany(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\BelongsToMany::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::belongsTo(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\BelongsTo::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::find(0),\n\t\tmap([\n\t\t\t'all' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'children' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'list' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'path' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'somethingCustom' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'threaded' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t\t'treeList' => \\Cake\\ORM\\Query\\SelectQuery::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::getBehavior(),\n\t\tmap([\n\t\t\t'CounterCache' => \\Cake\\ORM\\Behavior\\CounterCacheBehavior::class,\n\t\t\t'My' => \\MyNamespace\\MyPlugin\\Model\\Behavior\\MyBehavior::class,\n\t\t\t'Nullable' => \\Shim\\Model\\Behavior\\NullableBehavior::class,\n\t\t\t'Timestamp' => \\Cake\\ORM\\Behavior\\TimestampBehavior::class,\n\t\t\t'Translate' => \\Cake\\ORM\\Behavior\\TranslateBehavior::class,\n\t\t\t'Tree' => \\Cake\\ORM\\Behavior\\TreeBehavior::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::hasBehavior(),\n\t\t0,\n\t\t'CounterCache',\n\t\t'My',\n\t\t'Nullable',\n\t\t'Timestamp',\n\t\t'Translate',\n\t\t'Tree',\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::hasMany(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\HasMany::class,\n\t\t]),\n\t);\n\n\toverride(\n\t\t\\Cake\\ORM\\Table::hasOne(0),\n\t\tmap([\n\t\t\t'Awesome.Houses' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Awesome.Windows' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'BarBars' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'BarBarsAbstract' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Callbacks' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Cars' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Controllers.Houses' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'CustomFinder' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Exceptions' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Foos' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'MyNamespace/MyPlugin.My' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Relations.Bars' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Relations.Foos' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Relations.Users' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'SkipMe' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'SkipSome' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'Wheels' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t\t'WheelsExtra' => \\Cake\\ORM\\Association\\HasOne::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\ORM\\Table::removeBehavior(),\n\t\t0,\n\t\t'CounterCache',\n\t\t'My',\n\t\t'Nullable',\n\t\t'Timestamp',\n\t\t'Translate',\n\t\t'Tree',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Routing\\Router::pathUrl(),\n\t\t0,\n\t\targumentsSet('routePaths'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\TestSuite\\TestCase::addFixture(),\n\t\t0,\n\t\t'app.SmallWindows',\n\t\t'core.Posts',\n\t\t'plugin.IdeHelper.Cars',\n\t\t'plugin.MyNamespace/MyPlugin.Sub/My',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyArray(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyDate(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyDateTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyFile(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyFor(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyString(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::allowEmptyTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyArray(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyDate(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyDateTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyFile(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyString(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::notEmptyTime(),\n\t\t2,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\Validation\\Validator::requirePresence(),\n\t\t1,\n\t\targumentsSet('validationWhen'),\n\t);\n\n\toverride(\n\t\t\\Cake\\View\\CellTrait::cell(),\n\t\tmap([\n\t\t\t'Test' => \\TestApp\\View\\Cell\\TestCell::class,\n\t\t\t'Test::custom' => \\TestApp\\View\\Cell\\TestCell::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\Helper\\FormHelper::control(),\n\t\t0,\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'modified',\n\t\t'name',\n\t\t'params',\n\t\t'status',\n\t\t'user_id',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\Helper\\HtmlHelper::linkFromPath(),\n\t\t1,\n\t\targumentsSet('routePaths'),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\Helper\\UrlHelper::buildFromPath(),\n\t\t0,\n\t\targumentsSet('routePaths'),\n\t);\n\n\toverride(\n\t\t\\Cake\\View\\View::addHelper(0),\n\t\tmap([\n\t\t\t'Breadcrumbs' => \\Cake\\View\\Helper\\BreadcrumbsHelper::class,\n\t\t\t'Flash' => \\Cake\\View\\Helper\\FlashHelper::class,\n\t\t\t'Form' => \\Cake\\View\\Helper\\FormHelper::class,\n\t\t\t'Html' => \\TestApp\\View\\Helper\\HtmlHelper::class,\n\t\t\t'IdeHelper.DocBlock' => \\IdeHelper\\View\\Helper\\DocBlockHelper::class,\n\t\t\t'My' => \\TestApp\\View\\Helper\\MyHelper::class,\n\t\t\t'MyMethod' => \\TestApp\\View\\Helper\\MyMethodHelper::class,\n\t\t\t'Number' => \\Cake\\View\\Helper\\NumberHelper::class,\n\t\t\t'Paginator' => \\Cake\\View\\Helper\\PaginatorHelper::class,\n\t\t\t'Shim.Configure' => \\Shim\\View\\Helper\\ConfigureHelper::class,\n\t\t\t'Shim.Cookie' => \\Shim\\View\\Helper\\CookieHelper::class,\n\t\t\t'Text' => \\Cake\\View\\Helper\\TextHelper::class,\n\t\t\t'Time' => \\Cake\\View\\Helper\\TimeHelper::class,\n\t\t\t'Url' => \\Cake\\View\\Helper\\UrlHelper::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\View::element(),\n\t\t0,\n\t\t'Awesome.pagination',\n\t\t'deeply/nested',\n\t\t'example',\n\t);\n\n\toverride(\n\t\t\\Cake\\View\\View::loadHelper(0),\n\t\tmap([\n\t\t\t'Breadcrumbs' => \\Cake\\View\\Helper\\BreadcrumbsHelper::class,\n\t\t\t'Flash' => \\Cake\\View\\Helper\\FlashHelper::class,\n\t\t\t'Form' => \\Cake\\View\\Helper\\FormHelper::class,\n\t\t\t'Html' => \\TestApp\\View\\Helper\\HtmlHelper::class,\n\t\t\t'IdeHelper.DocBlock' => \\IdeHelper\\View\\Helper\\DocBlockHelper::class,\n\t\t\t'My' => \\TestApp\\View\\Helper\\MyHelper::class,\n\t\t\t'MyMethod' => \\TestApp\\View\\Helper\\MyMethodHelper::class,\n\t\t\t'Number' => \\Cake\\View\\Helper\\NumberHelper::class,\n\t\t\t'Paginator' => \\Cake\\View\\Helper\\PaginatorHelper::class,\n\t\t\t'Shim.Configure' => \\Shim\\View\\Helper\\ConfigureHelper::class,\n\t\t\t'Shim.Cookie' => \\Shim\\View\\Helper\\CookieHelper::class,\n\t\t\t'Text' => \\Cake\\View\\Helper\\TextHelper::class,\n\t\t\t'Time' => \\Cake\\View\\Helper\\TimeHelper::class,\n\t\t\t'Url' => \\Cake\\View\\Helper\\UrlHelper::class,\n\t\t]),\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\ViewBuilder::addHelper(),\n\t\t0,\n\t\t'Breadcrumbs',\n\t\t'Flash',\n\t\t'Form',\n\t\t'Html',\n\t\t'IdeHelper.DocBlock',\n\t\t'My',\n\t\t'MyMethod',\n\t\t'Number',\n\t\t'Paginator',\n\t\t'Shim.Configure',\n\t\t'Shim.Cookie',\n\t\t'Text',\n\t\t'Time',\n\t\t'Url',\n\t);\n\n\texpectedArguments(\n\t\t\\Cake\\View\\ViewBuilder::setLayout(),\n\t\t0,\n\t\t'ajax',\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseMigration::hasTable(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseMigration::table(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseSeed::hasTable(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\BaseSeed::table(),\n\t\t0,\n\t\targumentsSet('tableNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::addColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::addColumn(),\n\t\t1,\n\t\targumentsSet('columnTypes'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::changeColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::changeColumn(),\n\t\t1,\n\t\targumentsSet('columnTypes'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::hasColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::removeColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::renameColumn(),\n\t\t0,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Migrations\\Db\\Table::renameColumn(),\n\t\t1,\n\t\targumentsSet('columnNames'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Bar::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Bar'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\Foo::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::get(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::has(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\Relations\\Model\\Entity\\User::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:Relations\\Model\\Entity\\User'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBar::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBar'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\BarBarsAbstract::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\BarBarsAbstract'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Car::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Car'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Foo::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Foo'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::get(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::getError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::getInvalidField(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::getOriginal(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::has(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::hasValue(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::isDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::isEmpty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::setDirty(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\TestApp\\Model\\Entity\\Wheel::setError(),\n\t\t0,\n\t\targumentsSet('entityFields:TestApp\\Model\\Entity\\Wheel'),\n\t);\n\n\texpectedArguments(\n\t\t\\__d(),\n\t\t0,\n\t\t'awesome',\n\t\t'cake',\n\t\t'controllers',\n\t\t'ide_helper',\n\t\t'my_namespace/my_plugin',\n\t\t'relations',\n\t\t'shim',\n\t);\n\n\texpectedArguments(\n\t\t\\env(),\n\t\t0,\n\t\t'HTTP_HOST',\n\t);\n\n\texpectedArguments(\n\t\t\\urlArray(),\n\t\t0,\n\t\targumentsSet('routePaths'),\n\t);\n\n\tregisterArgumentsSet(\n\t\t'cacheEngines',\n\t\t'_cake_model_',\n\t\t'_cake_translations_',\n\t\t'default',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'columnNames',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'columnTypes',\n\t\t'biginteger',\n\t\t'binary',\n\t\t'binaryuuid',\n\t\t'bit',\n\t\t'blob',\n\t\t'boolean',\n\t\t'char',\n\t\t'date',\n\t\t'datetime',\n\t\t'decimal',\n\t\t'double',\n\t\t'float',\n\t\t'integer',\n\t\t'json',\n\t\t'smallinteger',\n\t\t'string',\n\t\t'text',\n\t\t'time',\n\t\t'timestamp',\n\t\t'uuid',\n\t\t'year',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'configureKeys',\n\t\t'App',\n\t\t'App.encoding',\n\t\t'App.namespace',\n\t\t'App.paths',\n\t\t'App.paths.templates',\n\t\t'IdeHelper',\n\t\t'IdeHelper.skipDatabaseTables',\n\t\t'Shim',\n\t\t'Shim.deprecations',\n\t\t'debug',\n\t\t'plugins',\n\t\t'plugins.Bake',\n\t\t'plugins.Cake/TwigView',\n\t\t'plugins.Migrations',\n\t\t'plugins.Shim',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Cake\\ORM\\Entity',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Relations\\Model\\Entity\\Bar',\n\t\t'id',\n\t\t'name',\n\t\t'user',\n\t\t'user_id',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Relations\\Model\\Entity\\Foo',\n\t\t'id',\n\t\t'name',\n\t\t'params',\n\t\t'user',\n\t\t'user_id',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:Relations\\Model\\Entity\\User',\n\t\t'bar',\n\t\t'foo',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\BarBar',\n\t\t'content',\n\t\t'created',\n\t\t'foo',\n\t\t'houses',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\BarBarsAbstract',\n\t\t'content',\n\t\t'created',\n\t\t'foo',\n\t\t'houses',\n\t\t'id',\n\t\t'name',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\Car',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'modified',\n\t\t'name',\n\t\t'status',\n\t\t'wheels',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\Foo',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'name',\n\t\t'params',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'entityFields:TestApp\\Model\\Entity\\Wheel',\n\t\t'car',\n\t\t'content',\n\t\t'created',\n\t\t'id',\n\t\t'name',\n\t\t'virtual_one',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'routePaths',\n\t\t'Awesome.Admin/AwesomeHouses::openDoor',\n\t\t'Bar::index',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'tableNames',\n\t\t'wheels',\n\t);\n\n\tregisterArgumentsSet(\n\t\t'validationWhen',\n\t\t'create',\n\t\t'update',\n\t);\n\n}\n"
  },
  {
    "path": "tests/test_files/routes/after/empty.php",
    "content": "<?php\n/**\n * @var \\Cake\\Routing\\RouteBuilder $routes\n */\n\n$routes->prefix();\n"
  },
  {
    "path": "tests/test_files/routes/after/existing.php",
    "content": "<?php\n/**\n * @var \\Cake\\Routing\\RouteBuilder $routes\n */\n\n$routes->prefix();\n"
  },
  {
    "path": "tests/test_files/routes/after/outdated.php",
    "content": "<?php\n/**\n * @var \\Cake\\Routing\\RouteBuilder $routes\n */\n\n$routes->prefix();\n"
  },
  {
    "path": "tests/test_files/routes/before/empty.php",
    "content": "<?php\n\n$routes->prefix();\n"
  },
  {
    "path": "tests/test_files/routes/before/existing.php",
    "content": "<?php\n/**\n * @var \\Cake\\Routing\\RouteBuilder $routes\n */\n\n$routes->prefix();\n"
  },
  {
    "path": "tests/test_files/routes/before/outdated.php",
    "content": "<?php\n/**\n * @var Outdated $routes\n */\n\n$routes->prefix();\n"
  },
  {
    "path": "tests/test_files/templates/array.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var array $x\n * @var array<int> $ints\n * @var array{a: int, b: string|null}|null $shaped\n * @var mixed $foo\n */\n\tforeach ($x as $y) {\n\t\techo $y;\n\t}\n\tforeach ($foo as $int) {\n\t\techo $int;\n\t}\n?>\n<div>\n\t<?php foreach ($ints as $int) {\n\t\techo $int;\n\t} ?>\n\t<?php foreach ($shaped as $x) {\n\t\techo h($x);\n\t} ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/custom_view.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\CustomView $this\n * @var \\TestApp\\Model\\Entity\\Car[]|\\Cake\\Collection\\CollectionInterface $cars\n */\n?>\n<div>\n\t<?php foreach ($cars as $car) {} ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/edit.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var \\TestApp\\Model\\Entity\\Foo $foo\n */\n?>\n<div>\n\t<?php echo $this->Form->create($foo, []); ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/empty.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n */\n?>\n"
  },
  {
    "path": "tests/test_files/templates/existing.php",
    "content": "<?php\n/**\n * @license MIT\n */\n\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var array<int, int> $things\n * @var \\TestApp\\Model\\Entity\\Car[]|\\Cake\\Collection\\CollectionInterface $cars\n * @var \\TestApp\\Model\\Entity\\Wheel $wheel\n */\n?>\n<div>\n\t<?php foreach ($cars as $car) {} ?>\n\t<?php echo h($wheel->id); ?>\n\n\t<?php foreach ($things as $keyInt => $valueInt) {\n\t\techo h($keyInt + $valueInt);\n\t} ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/existing_strict.php",
    "content": "<?php declare(strict_types=1);\n/**\n * @license MIT\n */\n\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var \\TestApp\\Model\\Entity\\Wheel $wheel\n */\n?>\n<div>\n\t<?php echo h($wheel->id); ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/following_inline.php",
    "content": "<?php\n/**\n * @license MIT\n * @var \\TestApp\\View\\AppView $this\n * @var \\TestApp\\Model\\Entity\\Wheel $wheel\n */\n\n/** @var \\Authorization\\Identity $identity */\n$identity = $this->getRequest()\n\t->getAttribute('identity');\n?>\n<div>\n\t<?php echo h($wheel->id); ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/inline.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var \\TestApp\\Model\\Entity\\Wheel $wheel\n */\n?>\n<div>\n\t<?php echo h($wheel->id); ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/loop.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var \\TestApp\\Model\\Entity\\Car[]|\\Cake\\Collection\\CollectionInterface $cars\n * @var \\TestApp\\Model\\Entity\\Wheel $wheel\n */\nuse Something;\n?>\n<div>\n\t<?php foreach ($cars as $car) {} ?>\n\t<?php echo h($wheel->id); ?>\n\n\t<?php foreach ($wheel->foos as $foos): ?>\n\t\t<tr>\n\t\t\t<td><?= h($foos->x) ?></td>\n\t\t</tr>\n\t<?php endforeach; ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/multiline.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var array $x\n * @var array<int> $ints\n * @var array{\n * \t\ta: int,\n * \t\tb: string|null\n * }|null $shaped\n * @var array{\n * \t\tc: array{\n * \t\t\td: int|string,\n * \t\t\te: string|null\n * \t\t}\n * } $nested\n *\n * @var mixed $foo\n */\n\tforeach ($x as $y) {\n\t\techo $y;\n\t}\n\tforeach ($foo as $int) {\n\t\techo $int;\n\t}\n?>\n<div>\n\t<?php foreach ($ints as $int) {\n\t\techo $int;\n\t} ?>\n\t<?php foreach ($shaped as $x) {\n\t\techo h($x);\n\t} ?>\n\t<?php foreach ($nested['c'] as $subArray) {\n\t\techo h($x);\n\t} ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/outdated.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var \\Cake\\ORM\\Entity $car !\n * @var \\TestApp\\Model\\Entity\\Wheel[]|\\Cake\\Collection\\CollectionInterface $wheels\n * @var bool $bool\n */\n?>\n<div>\n\t<?php echo $this->Form->sth(); ?>\n\t<?php echo $car->id; ?>\n\t<?php foreach ($wheels as $wheel); ?>\n\n\t<?php echo $bool ? 'yes' : 'no'; ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/phpline.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var \\TestApp\\Model\\Entity\\Car[]|\\Cake\\Collection\\CollectionInterface $cars\n * @var \\TestApp\\Model\\Entity\\Wheel $wheel\n */\n?>\n<?php echo $this->MyHelper->foo(); ?>\n<div>\n\t<?php foreach ($cars as $car) {} ?>\n\t<?php echo h($wheel->id); ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/string_interpolation.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var mixed $name\n */\n$url = \"/some/path?param={$this->request->getData('field')}\";\n?>\n<div>\n\t<a href=\"<?php echo $url; ?>\">Test</a>\n\t<?php echo h($name); ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/templates/vars.php",
    "content": "<?php\n/**\n * @var \\TestApp\\View\\AppView $this\n * @var object $allCars\n * @var object $date\n * @var object $obj\n * @var \\TestApp\\Model\\Entity\\Foo[]|\\Cake\\Collection\\CollectionInterface $foos\n * @var \\TestApp\\Model\\Entity\\Wheel $wheel\n */\n\tif ($obj) {\n\t\techo $obj->foo();\n\t}\n\techo $this->Generator->generate(['obj' => $obj]);\n?>\n<div>\n\t<?php foreach ($allCars as $car) {\n\t\t$finalCarTime = $this->Helper->out($car->created);\n\t\techo $finalCarTime;\n\t} ?>\n\t<?php echo h($wheel->id); ?>\n\t<p>\n\t\t<?= $date->format(); ?>\n\t</p>\n\t<?php foreach ($allCars->engines as $i => $engine) {\n\t\techo h($engine);\n\t} ?>\n\t<?php foreach ($foos as $foo) {\n\t\techo h($foo->prop);\n\t} ?>\n</div>\n"
  },
  {
    "path": "tests/test_files/tests/BarControllerTest.attribute.php",
    "content": "<?php\nnamespace TestApp\\Test\\TestCase\\Controller;\n\nuse Cake\\TestSuite\\IntegrationTestCase;\nuse PHPUnit\\Framework\\Attributes\\UsesClass;\nuse TestApp\\Controller\\BarController;\n\n#[UsesClass(BarController::class)]\nclass BarControllerTest extends IntegrationTestCase {\n}\n"
  },
  {
    "path": "tests/test_files/tests/BarControllerTest.existing.php",
    "content": "<?php\nnamespace TestApp\\Test\\TestCase\\Controller;\n\nuse Cake\\TestSuite\\IntegrationTestCase;\n\n/**\n * @uses \\TestApp\\Controller\\BarController\n */\nclass BarControllerTest extends IntegrationTestCase {\n}\n"
  },
  {
    "path": "tests/test_files/tests/BarControllerTest.link.php",
    "content": "<?php\nnamespace TestApp\\Test\\TestCase\\Controller;\n\nuse Cake\\TestSuite\\IntegrationTestCase;\n\n/**\n * @link \\TestApp\\Controller\\BarController\n */\nclass BarControllerTest extends IntegrationTestCase {\n}\n"
  },
  {
    "path": "tests/test_files/tests/BarControllerTest.missing.php",
    "content": "<?php\nnamespace TestApp\\Test\\TestCase\\Controller;\n\nuse Cake\\TestSuite\\IntegrationTestCase;\n\nclass BarControllerTest extends IntegrationTestCase {\n}\n"
  }
]