diff --git a/README.md b/README.md index 0d58933..488b511 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,15 @@ A minimal configuration file is: } ``` -This will look for autoloadable definitions in `src/`, and also look in `vendor/`. It will pay attention to the `autoload` sections of `composer.json` inside the `vendor/` directory. +This will look for autoloadable definitions in `src/`, and also look in `vendor/`. +Projects in `vendor/` are only processed if they also contain a `hh_autoload.json` file. + +Previously we also supported projects without `hh_autoload.json` by simulating Composer's autoload behavior, but we no longer do because that mostly applied to PHP files which HHVM can no longer parse. The following settings are optional: - `"extraFiles": ["file1.hack"]` - files that should not be autoloaded, but should be `require()`ed by `vendor/autoload.hack`. This should be needed much less frequently than under Composer - `"includeVendor": false` - do not include `vendor/` definitions in `vendor/autoload.hack` - - `"autoloadFilesBehavior": "scan"|"exec"` - whether autoload `files` from vendor should be `scan`ned for definitions, or `exec`uted by `vendor/autoload.hack` - `scan` is the default, and generally favorable, but `exec` is needed if you have dependencies that need code to be executed on startup. `scan` is sufficient if your dependencies just use `files` because they need to define things that aren't classes, which is usually the case. - `"devRoots": [ "path/", ...]` - additional roots to only include in dev mode, not when installed as a dependency. - `"relativeAutoloadRoot": false` - do not use a path relative to `__DIR__` for autoloading. Instead, use the path to the folder containing `hh_autoload.json` when building the autoload map. - `"failureHandler:" classname` - use the specified class to handle definitions that aren't the Map. Your handler will not be invoked for functions or constants @@ -91,7 +93,7 @@ Information you may need is available from: How It Works ============ - - A parser (FactParse or DefinitionFinder) provides a list of all Hack definitions in the specified locations + - A parser (FactParse) provides a list of all Hack definitions in the specified locations - This is used to generate something similar to a classmap, except including other kinds of definitions - The map is provided to HHVM with [`HH\autoload_set_paths()`](https://docs.hhvm.com/hack/reference/function/HH.autoload_set_paths/) diff --git a/src/AutoloadFilesBehavior.hack b/src/AutoloadFilesBehavior.hack deleted file mode 100644 index 2526e7a..0000000 --- a/src/AutoloadFilesBehavior.hack +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -namespace Facebook\AutoloadMap; - -/** How to autoload files that composer is told to always require. */ -enum AutoloadFilesBehavior: string { - /** Scan the files for autoloadable definitions */ - FIND_DEFINITIONS = 'scan'; - /** Always require the files */ - EXEC_FILES = 'exec'; -} diff --git a/src/Config.hack b/src/Config.hack index 14c4e76..4b7afe6 100644 --- a/src/Config.hack +++ b/src/Config.hack @@ -13,7 +13,6 @@ namespace Facebook\AutoloadMap; type Config = shape( 'roots' => ImmVector, 'devRoots' => ImmVector, - 'autoloadFilesBehavior' => AutoloadFilesBehavior, 'includeVendor' => bool, 'extraFiles' => ImmVector, 'parser' => Parser, diff --git a/src/ConfigurationLoader.hack b/src/ConfigurationLoader.hack index 8f1e9d9..e5020cc 100644 --- a/src/ConfigurationLoader.hack +++ b/src/ConfigurationLoader.hack @@ -60,12 +60,6 @@ abstract final class ConfigurationLoader { 'devRoots', ), ), - 'autoloadFilesBehavior' => TypeAssert\is_nullable_enum( - AutoloadFilesBehavior::class, - $data['autoloadFilesBehavior'] ?? null, - 'autoloadFilesbehavior', - ) ?? - AutoloadFilesBehavior::FIND_DEFINITIONS, 'relativeAutoloadRoot' => TypeAssert\is_nullable_bool( $data['relativeAutoloadRoot'] ?? null, 'relativerAutoloadRoot', diff --git a/src/Merger.hack b/src/Merger.hack index 377ed03..c13cd61 100644 --- a/src/Merger.hack +++ b/src/Merger.hack @@ -14,7 +14,6 @@ namespace Facebook\AutoloadMap; * For example, we may merge: * - the root autoload map * - additional autoload maps for each vendored dependency - * - in the case of composer, a psr0, psr4, and classmap */ abstract final class Merger { /** Return a new map containing all the entries from the input maps. diff --git a/src/builders/ComposerImporter.hack b/src/builders/ComposerImporter.hack deleted file mode 100644 index b90b267..0000000 --- a/src/builders/ComposerImporter.hack +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -namespace Facebook\AutoloadMap; - -/** Create an autoload map for a directory based on the `composer.json` in that - * directory. - * - * This is used by default for projects in `vendor/` that do not have an - * `hh_autoload.json` */ -final class ComposerImporter implements Builder { - private string $root; - private Vector $builders = Vector {}; - private Set $excludes = Set {}; - private Vector $files = Vector {}; - - public function __construct(string $path, private Config $config) { - if (!\file_exists($path)) { - throw new Exception('%s does not exist', $path); - } - $this->root = \dirname($path); - $composer_json = \file_get_contents($path); - $composer_config = \json_decode($composer_json, /* assoc = */ true); - $composer_autoload = idx($composer_config, 'autoload'); - if ($composer_autoload === null) { - return; - } - - foreach ($composer_autoload as $key => $values) { - switch ($key) { - case 'psr-0': - $this->importPSR0($values); - break; - case 'psr-4': - $this->importPSR4($values); - break; - case 'classmap': - $this->importClassmap($values); - break; - case 'files': - $this->importFiles($values); - break; - case 'exclude-from-classmap': - foreach ($values as $value) { - $this->excludes[] = $this->root.'/'.$value; - } - break; - default: - throw new Exception( - "Don't understand how to deal with autoload section %s in %s", - $key, - $path, - ); - } - } - } - - public function getFiles(): ImmVector { - return $this->files->toImmVector(); - } - - public function getAutoloadMap(): AutoloadMap { - return Merger::merge( - $this->builders - ->map( - $builder ==> - new PathExclusionFilter($builder, $this->excludes->immutable()), - ) - ->map($builder ==> $builder->getAutoloadMap()), - ); - } - - /** Composer supports detecting classes inside files for given subtrees; this - * does the same for any directories configured that way. - */ - private function importClassmap(array $roots): void { - foreach ($roots as $root) { - $path = $this->root.'/'.$root; - try { - if (\is_dir($path)) { - $scanner = Scanner::fromTree($path, $this->config['parser']); - } else { - $scanner = Scanner::fromFile($path, $this->config['parser']); - } - } catch (\UnexpectedValueException $e) { - // Incorrectly configured configured path. - continue; - } - - $this->builders[] = new ClassesOnlyFilter($scanner); - } - } - - private function importPSR4(array $roots): void { - $roots = self::normalizePSRRoots($roots); - foreach ($roots as $prefix => $prefix_roots) { - foreach ($prefix_roots as $root) { - try { - $scanner = Scanner::fromTree( - $this->root.'/'.$root, - $this->config['parser'], - ); - } catch (\UnexpectedValueException $e) { - // Incorrectly configured configured path. - continue; - } - - $this->builders[] = new PSR4Filter( - $prefix, - $this->root.'/'.$root, - $scanner, - ); - } - } - } - - private function importPSR0(array $roots): void { - $roots = self::normalizePSRRoots($roots); - foreach ($roots as $prefix => $prefix_roots) { - foreach ($prefix_roots as $root) { - try { - $scanner = Scanner::fromTree( - $this->root.'/'.$root, - $this->config['parser'], - ); - } catch (\UnexpectedValueException $e) { - // Incorrectly configured configured path. - continue; - } - - $this->builders[] = new PSR0Filter( - $prefix, - $this->root.'/'.$root, - $scanner, - ); - } - } - } - - private static function normalizePSRRoots( - array $roots, - ): darray> { - $out = darray[]; - foreach ($roots as $k => $v) { - if ($v is string) { - $out[$k] ??= varray[]; - $out[$k][] = $v; - } else if (\is_array($v)) { - foreach ($v as $w) { - $out[$k] ??= varray[]; - $out[$k][] = $w; - } - } - } - return $out; - } - - private function importFiles(array $files): void { - foreach ($files as $file) { - $file = $this->root.'/'.$file; - if ( - $this->config['autoloadFilesBehavior'] === - AutoloadFilesBehavior::FIND_DEFINITIONS - ) { - try { - $this->builders[] = Scanner::fromFile($file, $this->config['parser']); - } catch (\UnexpectedValueException $e) { - // Incorrectly configured configured path. - } - } else { - $this->files[] = $file; - } - } - } -} diff --git a/src/builders/RootImporter.hack b/src/builders/RootImporter.hack index f7cc2c6..05c9a4e 100644 --- a/src/builders/RootImporter.hack +++ b/src/builders/RootImporter.hack @@ -13,8 +13,12 @@ namespace Facebook\AutoloadMap; * * This will: * - create an `HHImporter` for the current directory - * - create `ComposerImporter`s or `HHImporter`s for every project under - * `vendor/` + * - create `HHImporter`s for every project under `vendor/` that has + * `hh_autoload.json` + * + * Previously we also supported projects without `hh_autoload.json` by + * simulating Composer's autoload behavior, but we no longer do because that + * mostly applied to PHP files which HHVM can no longer parse. */ final class RootImporter implements Builder { private Vector $builders = Vector {}; @@ -38,12 +42,6 @@ final class RootImporter implements Builder { $dependency, IncludedRoots::PROD_ONLY, ); - continue; - } - $composer_json = $dependency.'/composer.json'; - if (\file_exists($composer_json)) { - $this->builders[] = new ComposerImporter($composer_json, $config); - continue; } } } diff --git a/src/filters/BasePSRFilter.hack b/src/filters/BasePSRFilter.hack deleted file mode 100644 index 983de8d..0000000 --- a/src/filters/BasePSRFilter.hack +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -namespace Facebook\AutoloadMap; - -/** Base class for only removing non-PSR-compliant definitions from another - * builder. - * - * @see `PSR0Filter` and `PSR4Filter` - */ -abstract class BasePSRFilter implements Builder { - - /** Return the path of the file that is expected to contain the specified - * a class. - * - * @param $classname the fully-qualified classname - * @param $prefix the prefix/namespace for this PSR configuration - * @param $root the root directory for classes with the specified prefix - */ - abstract protected static function getExpectedPathWithoutExtension( - string $classname, - string $prefix, - string $root, - ): string; - - /** Create a new `BasePSRFilter` - * - * @param $prefix the prefix/namespace for this PSR configuration - * @param $root the root directory for classes with the specified prefix - * @param $source a `Builder` containing definitions that will be filtered - */ - final public function __construct( - private string $prefix, - private string $root, - private Builder $source, - ) { - $this->root = \rtrim($this->root, '/').'/'; - } - - public function getFiles(): ImmVector { - return ImmVector {}; - } - - public function getAutoloadMap(): AutoloadMap { - $classes = ( - new Map($this->source->getAutoloadMap()['class']) - )->filterWithKey( - (string $class_name, string $file): bool ==> { - if ( - $this->prefix !== '' && \stripos($class_name, $this->prefix) !== 0 - ) { - return false; - } - $expected = static::getExpectedPathWithoutExtension( - $class_name, - $this->prefix, - $this->root, - ); - $expected = \strtolower($expected); - $file = \strtolower($file); - return ($file === $expected.'.hh' || $file === $expected.'.php'); - }, - ); - - return shape( - 'class' => darray($classes), - 'function' => darray[], - 'type' => darray[], - 'constant' => darray[], - ); - } -} diff --git a/src/filters/ClassesOnlyFilter.hack b/src/filters/ClassesOnlyFilter.hack deleted file mode 100644 index da2925f..0000000 --- a/src/filters/ClassesOnlyFilter.hack +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -namespace Facebook\AutoloadMap; - -/** Filter out all definitions except for classes, interfaces, etc. */ -final class ClassesOnlyFilter implements Builder { - /** Create a new `ClassesOnlyFilter` - * - * @param $source the builder containing class definitions - */ - public function __construct(private Builder $source) { - } - - public function getFiles(): ImmVector { - return ImmVector {}; - } - - public function getAutoloadMap(): AutoloadMap { - return shape( - 'class' => $this->source->getAutoloadMap()['class'], - 'function' => darray[], - 'type' => darray[], - 'constant' => darray[], - ); - } -} diff --git a/src/filters/PSR0Filter.hack b/src/filters/PSR0Filter.hack deleted file mode 100644 index 04a7ef2..0000000 --- a/src/filters/PSR0Filter.hack +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -namespace Facebook\AutoloadMap; - -/** - * A filter exposing any definitions from another builder that are compliant - * with PSR-0. - */ -final class PSR0Filter extends BasePSRFilter { - <<__Override>> - protected static function getExpectedPathWithoutExtension( - string $class_name, - string $prefix, - string $root, - ): string { - $class_name = \strtr($class_name, '\\', '/'); - - // Underscores in namespace parts must be ignored, but those in the class - // name need to be converted. - $namespace = ''; - $last_namespace_sep = \strrpos($class_name, '/'); - if ($last_namespace_sep !== false) { - $namespace = \substr($class_name, 0, $last_namespace_sep + 1); - $class_name = \substr($class_name, $last_namespace_sep + 1); - } - - return $root.$namespace.\strtr($class_name, '_', '/'); - } -} diff --git a/src/filters/PSR4Filter.hack b/src/filters/PSR4Filter.hack deleted file mode 100644 index f9f4ad2..0000000 --- a/src/filters/PSR4Filter.hack +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -namespace Facebook\AutoloadMap; - -/** A filter exposing any definitions from a source builder that - * are compliant with PSR-4 */ -final class PSR4Filter extends BasePSRFilter { - <<__Override>> - protected static function getExpectedPathWithoutExtension( - string $class_name, - string $prefix, - string $root, - ): string { - $local_part = \str_ireplace($prefix, '', $class_name); - $expected = $root.\strtr($local_part, "\\", '/'); - return $expected; - } -} diff --git a/src/filters/PathExclusionFilter.hack b/src/filters/PathExclusionFilter.hack deleted file mode 100644 index a5963b6..0000000 --- a/src/filters/PathExclusionFilter.hack +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -namespace Facebook\AutoloadMap; - -/** - * Remove any definitions from files that aren't in a specified prefix. - */ -final class PathExclusionFilter implements Builder { - public function __construct( - private Builder $source, - private ImmSet $prefixes, - ) { - } - - public function getFiles(): ImmVector { - return ImmVector {}; - } - - public function getAutoloadMap(): AutoloadMap { - $map = $this->source->getAutoloadMap(); - return shape( - 'class' => $this->filter($map['class']), - 'function' => $this->filter($map['function']), - 'type' => $this->filter($map['type']), - 'constant' => $this->filter($map['constant']), - ); - } - - private function filter(array $map): array { - return \array_filter( - $map, - (string $path): bool ==> { - foreach ($this->prefixes as $prefix) { - if (\strpos($path, $prefix) === 0) { - return false; - } - } - return true; - }, - ); - } -} diff --git a/tests/ConfigurationLoaderTest.hack b/tests/ConfigurationLoaderTest.hack index 8240735..88fa126 100644 --- a/tests/ConfigurationLoaderTest.hack +++ b/tests/ConfigurationLoaderTest.hack @@ -13,10 +13,12 @@ use type Facebook\HackTest\DataProvider; use function Facebook\FBExpect\expect; final class ConfigurationLoaderTest extends \Facebook\HackTest\HackTest { + const IGNORED_VALUE = '__ignore__'; + public function goodTestCases(): array>> { return darray[ 'fully specified' => varray[darray[ - 'autoloadFilesBehavior' => AutoloadFilesBehavior::EXEC_FILES, + 'autoloadFilesBehavior' => self::IGNORED_VALUE, 'relativeAutoloadRoot' => false, 'includeVendor' => false, 'extraFiles' => varray[], @@ -57,12 +59,11 @@ final class ConfigurationLoaderTest extends \Facebook\HackTest\HackTest { ): void { expect($config['roots']->toArray())->toBePHPEqual($data['roots']); - expect(AutoloadFilesBehavior::coerce($config['autoloadFilesBehavior'])) - ->toNotBeNull(); - $config = Shapes::toArray($config); foreach ($data as $key => $value) { - if (\is_array($value)) { + if ($value === self::IGNORED_VALUE) { + expect($config)->toNotContainKey($key); + } else if (\is_array($value)) { $value = new ImmVector($value); expect($config[$key])->toBePHPEqual($value); } else { diff --git a/tests/fixtures/bootstrap_test.php b/tests/fixtures/bootstrap_test.php deleted file mode 100644 index 23ad038..0000000 --- a/tests/fixtures/bootstrap_test.php +++ /dev/null @@ -1,17 +0,0 @@ -> function */ require($argv[1]); - -$x = \Facebook\AutoloadMap\AutoloadFilesBehavior::FIND_DEFINITIONS; - -print($x); diff --git a/tests/fixtures/hh-only/hh_autoload.json b/tests/fixtures/hh-only/hh_autoload.json index 6fb4aa2..e88d413 100644 --- a/tests/fixtures/hh-only/hh_autoload.json +++ b/tests/fixtures/hh-only/hh_autoload.json @@ -7,5 +7,6 @@ ], "extraFiles": [ "extrafile.php" - ] + ], + "invalidKey": "should be ignored" } diff --git a/tests/fixtures/psr-0/composer.json b/tests/fixtures/psr-0/composer.json deleted file mode 100644 index 36105c4..0000000 --- a/tests/fixtures/psr-0/composer.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "autoload": { - "psr-0": { - "": "src-without-prefix", - "PSR0Test": "src", - "PSR0TestWithSlash": "src-with-slash", - "PSR0_Test_With_Underscores": "src-with-underscores" - } - } -} diff --git a/tests/fixtures/psr-0/src-with-slash/PSR0TestWithSlash.php b/tests/fixtures/psr-0/src-with-slash/PSR0TestWithSlash.php deleted file mode 100644 index 4160a41..0000000 --- a/tests/fixtures/psr-0/src-with-slash/PSR0TestWithSlash.php +++ /dev/null @@ -1,11 +0,0 @@ -