From 39890895a8983a3b562fb6f90c5901960e2e002d Mon Sep 17 00:00:00 2001 From: brendt Date: Wed, 20 Mar 2024 05:48:38 +0100 Subject: [PATCH] Single and double quote too greedy --- .../Patterns/JsDoubleQuoteValuePattern.php | 11 +- .../Patterns/JsSingleQuoteValuePattern.php | 10 +- .../Patterns/JsonDoubleQuoteValuePattern.php | 2 +- .../Php/Patterns/DoubleQuoteValuePattern.php | 11 +- .../Php/Patterns/SingleQuoteValuePattern.php | 10 +- src/Languages/Php/PhpLanguage.php | 8 +- .../Patterns/SqlDoubleQuoteValuePattern.php | 7 +- .../Patterns/SqlSingleQuoteValuePattern.php | 7 +- .../Patterns/YamlDoubleQuoteValuePattern.php | 2 +- .../Patterns/YamlSingleQuoteValuePattern.php | 2 +- src/Tokens/GroupTokens.php | 6 +- src/Tokens/Token.php | 4 +- tests/Languages/Php/PhpLanguageTest.php | 12 + tests/TokenTest.php | 4 +- tests/index.php | 2 +- tests/test-big.md | 1496 +++++++++++++++++ tests/test.md | 34 +- 17 files changed, 1541 insertions(+), 87 deletions(-) create mode 100644 tests/test-big.md diff --git a/src/Languages/JavaScript/Patterns/JsDoubleQuoteValuePattern.php b/src/Languages/JavaScript/Patterns/JsDoubleQuoteValuePattern.php index 1614097..11130ab 100644 --- a/src/Languages/JavaScript/Patterns/JsDoubleQuoteValuePattern.php +++ b/src/Languages/JavaScript/Patterns/JsDoubleQuoteValuePattern.php @@ -13,22 +13,13 @@ input: 'return "hello"', output: 'hello', )] - -#[PatternTest( - input: 'return "hello - - world"', - output: 'hello - - world', -)] final readonly class JsDoubleQuoteValuePattern implements Pattern { use IsPattern; public function getPattern(): string { - return '"(?(\n|.)*?)"'; + return '"(?.*?)"'; } public function getTokenType(): TokenType diff --git a/src/Languages/JavaScript/Patterns/JsSingleQuoteValuePattern.php b/src/Languages/JavaScript/Patterns/JsSingleQuoteValuePattern.php index 4d0af31..b23040c 100644 --- a/src/Languages/JavaScript/Patterns/JsSingleQuoteValuePattern.php +++ b/src/Languages/JavaScript/Patterns/JsSingleQuoteValuePattern.php @@ -13,21 +13,13 @@ input: "return 'hello';", output: 'hello', )] -#[PatternTest( - input: "return 'hello - - world';", - output: 'hello - - world', -)] final readonly class JsSingleQuoteValuePattern implements Pattern { use IsPattern; public function getPattern(): string { - return "'(?(\n|.)*?)'"; + return "'(?.*?)'"; } public function getTokenType(): TokenType diff --git a/src/Languages/Json/Patterns/JsonDoubleQuoteValuePattern.php b/src/Languages/Json/Patterns/JsonDoubleQuoteValuePattern.php index 5e6c868..ac1d7f7 100644 --- a/src/Languages/Json/Patterns/JsonDoubleQuoteValuePattern.php +++ b/src/Languages/Json/Patterns/JsonDoubleQuoteValuePattern.php @@ -16,7 +16,7 @@ public function getPattern(): string { - return '(?\"(.|\n)*?\")'; + return '(?\".*?\")'; } public function getTokenType(): TokenType diff --git a/src/Languages/Php/Patterns/DoubleQuoteValuePattern.php b/src/Languages/Php/Patterns/DoubleQuoteValuePattern.php index aaf80c3..aa7add6 100644 --- a/src/Languages/Php/Patterns/DoubleQuoteValuePattern.php +++ b/src/Languages/Php/Patterns/DoubleQuoteValuePattern.php @@ -13,22 +13,13 @@ input: 'return "hello"', output: 'hello', )] - -#[PatternTest( - input: 'return "hello - - world"', - output: 'hello - - world', -)] final readonly class DoubleQuoteValuePattern implements Pattern { use IsPattern; public function getPattern(): string { - return '"(?(\n|.)*?)"'; + return '"(?.*?)"'; } public function getTokenType(): TokenType diff --git a/src/Languages/Php/Patterns/SingleQuoteValuePattern.php b/src/Languages/Php/Patterns/SingleQuoteValuePattern.php index fa8bf17..eca6fe8 100644 --- a/src/Languages/Php/Patterns/SingleQuoteValuePattern.php +++ b/src/Languages/Php/Patterns/SingleQuoteValuePattern.php @@ -13,21 +13,13 @@ input: "return 'hello';", output: 'hello', )] -#[PatternTest( - input: "return 'hello - - world';", - output: 'hello - - world', -)] final readonly class SingleQuoteValuePattern implements Pattern { use IsPattern; public function getPattern(): string { - return "'(?(\n|.)*?)'"; + return "'(?.*?)'"; } public function getTokenType(): TokenType diff --git a/src/Languages/Php/PhpLanguage.php b/src/Languages/Php/PhpLanguage.php index f05fd83..e15edfb 100644 --- a/src/Languages/Php/PhpLanguage.php +++ b/src/Languages/Php/PhpLanguage.php @@ -131,10 +131,6 @@ public function getPatterns(): array new MultilineSingleDocCommentPattern(), new SinglelineCommentPattern(), - // VALUES - new SingleQuoteValuePattern(), - new DoubleQuoteValuePattern(), - // TYPES new AttributeTypePattern(), new ImplementsPattern(), @@ -158,6 +154,10 @@ public function getPatterns(): array new FunctionCallPattern(), new ConstantPropertyPattern(), new ConstantNamePattern(), + + // VALUES + new SingleQuoteValuePattern(), + new DoubleQuoteValuePattern(), ]; } } diff --git a/src/Languages/Sql/Patterns/SqlDoubleQuoteValuePattern.php b/src/Languages/Sql/Patterns/SqlDoubleQuoteValuePattern.php index e12f47b..b3ddf24 100644 --- a/src/Languages/Sql/Patterns/SqlDoubleQuoteValuePattern.php +++ b/src/Languages/Sql/Patterns/SqlDoubleQuoteValuePattern.php @@ -10,18 +10,13 @@ use Tempest\Highlight\Tokens\TokenType; #[PatternTest(input: 'bar = "baz"', output: 'baz')] -#[PatternTest(input: 'bar = "ba - -z"', output: 'ba - -z')] final readonly class SqlDoubleQuoteValuePattern implements Pattern { use IsPattern; public function getPattern(): string { - return '\"(?(.|\n)*?)\"'; + return '\"(?.*?)\"'; } public function getTokenType(): TokenType diff --git a/src/Languages/Sql/Patterns/SqlSingleQuoteValuePattern.php b/src/Languages/Sql/Patterns/SqlSingleQuoteValuePattern.php index 9b34344..0ea2281 100644 --- a/src/Languages/Sql/Patterns/SqlSingleQuoteValuePattern.php +++ b/src/Languages/Sql/Patterns/SqlSingleQuoteValuePattern.php @@ -10,18 +10,13 @@ use Tempest\Highlight\Tokens\TokenType; #[PatternTest(input: "bar = 'baz'", output: 'baz')] -#[PatternTest(input: "bar = 'ba - -z'", output: 'ba - -z')] final readonly class SqlSingleQuoteValuePattern implements Pattern { use IsPattern; public function getPattern(): string { - return '\'(?(.|\n)*?)\''; + return '\'(?.*?)\''; } public function getTokenType(): TokenType diff --git a/src/Languages/Yaml/Patterns/YamlDoubleQuoteValuePattern.php b/src/Languages/Yaml/Patterns/YamlDoubleQuoteValuePattern.php index 97831c7..0c38687 100644 --- a/src/Languages/Yaml/Patterns/YamlDoubleQuoteValuePattern.php +++ b/src/Languages/Yaml/Patterns/YamlDoubleQuoteValuePattern.php @@ -16,7 +16,7 @@ public function getPattern(): string { - return '\"(?(.|\n)*?)\"'; + return '\"(?.*?)\"'; } public function getTokenType(): TokenType diff --git a/src/Languages/Yaml/Patterns/YamlSingleQuoteValuePattern.php b/src/Languages/Yaml/Patterns/YamlSingleQuoteValuePattern.php index f6e92b7..53f5981 100644 --- a/src/Languages/Yaml/Patterns/YamlSingleQuoteValuePattern.php +++ b/src/Languages/Yaml/Patterns/YamlSingleQuoteValuePattern.php @@ -16,7 +16,7 @@ public function getPattern(): string { - return '\'(?(.|\n)*?)\''; + return '\'(?.*?)\''; } public function getTokenType(): TokenType diff --git a/src/Tokens/GroupTokens.php b/src/Tokens/GroupTokens.php index 462c61c..2f72835 100644 --- a/src/Tokens/GroupTokens.php +++ b/src/Tokens/GroupTokens.php @@ -6,6 +6,10 @@ final readonly class GroupTokens { + /** + * @param \Tempest\Highlight\Tokens\Token[] $tokens + * @return \Tempest\Highlight\Tokens\Token[] + */ public function __invoke(array $tokens): array { // Sort tokens in the right order @@ -25,7 +29,7 @@ public function __invoke(array $tokens): array $token = $token->cloneWithoutParent(); foreach ($tokens as $compareKey => $compareToken) { - if ($token->contains($compareToken)) { + if ($token->containsOrOverlaps($compareToken)) { if ($token->canContain($compareToken)) { $token->addChild($compareToken); } diff --git a/src/Tokens/Token.php b/src/Tokens/Token.php index 4395afd..16a5cfa 100644 --- a/src/Tokens/Token.php +++ b/src/Tokens/Token.php @@ -33,12 +33,12 @@ public function equals(Token $other): bool // && $this->type === $other->type; } - public function contains(Token $other): bool + public function containsOrOverlaps(Token $other): bool { return ! $this->equals($other) && $this->start <= $other->start - && $this->end >= $other->end; + && $other->start < $this->end; } public function addChild(Token $child): void diff --git a/tests/Languages/Php/PhpLanguageTest.php b/tests/Languages/Php/PhpLanguageTest.php index 317213e..347bc7a 100644 --- a/tests/Languages/Php/PhpLanguageTest.php +++ b/tests/Languages/Php/PhpLanguageTest.php @@ -29,6 +29,18 @@ public static function data(): array ["public string|\Stringable \$message;", 'public string|\Stringable $message;'], ['for($x = 0; $x < 150; $x++) {', 'for($x = 0; $x < 150; $x++) {'], ["'namespace ';", "'namespace ';"], + [ + "// We'll + +foo() + +// We'll", + '// We\'ll + +foo() + +// We\'ll', + ], ]; } } diff --git a/tests/TokenTest.php b/tests/TokenTest.php index a01fe5c..ce2ab20 100644 --- a/tests/TokenTest.php +++ b/tests/TokenTest.php @@ -26,7 +26,7 @@ public function test_contains(): void type: TokenType::VALUE, ); - $this->assertTrue($a->contains($b)); - $this->assertFalse($b->contains($a)); + $this->assertTrue($a->containsOrOverlaps($b)); + $this->assertFalse($b->containsOrOverlaps($a)); } } diff --git a/tests/index.php b/tests/index.php index a454195..3b190ba 100644 --- a/tests/index.php +++ b/tests/index.php @@ -20,7 +20,7 @@ $markdown = new MarkdownConverter($environment); -$contents = $markdown->convert(file_get_contents(__DIR__ . '/test.md'))->getContent(); +$contents = $markdown->convert(file_get_contents(__DIR__ . '/test-big.md'))->getContent(); ?> diff --git a/tests/test-big.md b/tests/test-big.md new file mode 100644 index 0000000..8fa0049 --- /dev/null +++ b/tests/test-big.md @@ -0,0 +1,1496 @@ +```php +getAlias($c); + } + + return new ContextualBindingBuilder($this, $aliases); + } + + /** + * Determine if the given abstract type has been bound. + * + * @param string $abstract + * @return bool + */ + public function bound($abstract) + { + return isset($this->bindings[$abstract]) || + isset($this->instances[$abstract]) || + $this->isAlias($abstract); + } + + /** + * {@inheritdoc} + * + * @return bool + */ + public function has(string $id): bool + { + return $this->bound($id); + } + + /** + * Determine if the given abstract type has been resolved. + * + * @param string $abstract + * @return bool + */ + public function resolved($abstract) + { + if ($this->isAlias($abstract)) { + $abstract = $this->getAlias($abstract); + } + + return isset($this->resolved[$abstract]) || + isset($this->instances[$abstract]); + } + + /** + * Determine if a given type is shared. + * + * @param string $abstract + * @return bool + */ + public function isShared($abstract) + { + return isset($this->instances[$abstract]) || + (isset($this->bindings[$abstract]['shared']) && + $this->bindings[$abstract]['shared'] === true); + } + + /** + * Determine if a given string is an alias. + * + * @param string $name + * @return bool + */ + public function isAlias($name) + { + return isset($this->aliases[$name]); + } + + /** + * Register a binding with the container. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @param bool $shared + * @return void + * + * @throws \TypeError + */ + public function bind($abstract, $concrete = null, $shared = false) + { + $this->dropStaleInstances($abstract); + + // If no concrete type was given, we will simply set the concrete type to the + // abstract type. After that, the concrete type to be registered as shared + // without being forced to state their classes in both of the parameters. + if (is_null($concrete)) { + $concrete = $abstract; + } + + // If the factory is not a Closure, it means it is just a class name which is + // bound into this container to the abstract type and we will just wrap it + // up inside its own Closure to give us more convenience when extending. + if (! $concrete instanceof Closure) { + if (! is_string($concrete)) { + throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null'); + } + + $concrete = $this->getClosure($abstract, $concrete); + } + + $this->bindings[$abstract] = compact('concrete', 'shared'); + + // If the abstract type was already resolved in this container we'll fire the + // rebound listener so that any objects which have already gotten resolved + // can have their copy of the object updated via the listener callbacks. + if ($this->resolved($abstract)) { + $this->rebound($abstract); + } + } + + /** + * Get the Closure to be used when building a type. + * + * @param string $abstract + * @param string $concrete + * @return \Closure + */ + protected function getClosure($abstract, $concrete) + { + return function ($container, $parameters = []) use ($abstract, $concrete) { + if ($abstract == $concrete) { + return $container->build($concrete); + } + + return $container->resolve( + $concrete, $parameters, $raiseEvents = false + ); + }; + } + + /** + * Determine if the container has a method binding. + * + * @param string $method + * @return bool + */ + public function hasMethodBinding($method) + { + return isset($this->methodBindings[$method]); + } + + /** + * Bind a callback to resolve with Container::call. + * + * @param array|string $method + * @param \Closure $callback + * @return void + */ + public function bindMethod($method, $callback) + { + $this->methodBindings[$this->parseBindMethod($method)] = $callback; + } + + /** + * Get the method to be bound in class@method format. + * + * @param array|string $method + * @return string + */ + protected function parseBindMethod($method) + { + if (is_array($method)) { + return $method[0].'@'.$method[1]; + } + + return $method; + } + + /** + * Get the method binding for the given method. + * + * @param string $method + * @param mixed $instance + * @return mixed + */ + public function callMethodBinding($method, $instance) + { + return call_user_func($this->methodBindings[$method], $instance, $this); + } + + /** + * Add a contextual binding to the container. + * + * @param string $concrete + * @param string $abstract + * @param \Closure|string $implementation + * @return void + */ + public function addContextualBinding($concrete, $abstract, $implementation) + { + $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation; + } + + /** + * Register a binding if it hasn't already been registered. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @param bool $shared + * @return void + */ + public function bindIf($abstract, $concrete = null, $shared = false) + { + if (! $this->bound($abstract)) { + $this->bind($abstract, $concrete, $shared); + } + } + + /** + * Register a shared binding in the container. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function singleton($abstract, $concrete = null) + { + $this->bind($abstract, $concrete, true); + } + + /** + * Register a shared binding if it hasn't already been registered. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function singletonIf($abstract, $concrete = null) + { + if (! $this->bound($abstract)) { + $this->singleton($abstract, $concrete); + } + } + + /** + * Register a scoped binding in the container. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function scoped($abstract, $concrete = null) + { + $this->scopedInstances[] = $abstract; + + $this->singleton($abstract, $concrete); + } + + /** + * Register a scoped binding if it hasn't already been registered. + * + * @param string $abstract + * @param \Closure|string|null $concrete + * @return void + */ + public function scopedIf($abstract, $concrete = null) + { + if (! $this->bound($abstract)) { + $this->scoped($abstract, $concrete); + } + } + + /** + * "Extend" an abstract type in the container. + * + * @param string $abstract + * @param \Closure $closure + * @return void + * + * @throws \InvalidArgumentException + */ + public function extend($abstract, Closure $closure) + { + $abstract = $this->getAlias($abstract); + + if (isset($this->instances[$abstract])) { + $this->instances[$abstract] = $closure($this->instances[$abstract], $this); + + $this->rebound($abstract); + } else { + $this->extenders[$abstract][] = $closure; + + if ($this->resolved($abstract)) { + $this->rebound($abstract); + } + } + } + + /** + * Register an existing instance as shared in the container. + * + * @param string $abstract + * @param mixed $instance + * @return mixed + */ + public function instance($abstract, $instance) + { + $this->removeAbstractAlias($abstract); + + $isBound = $this->bound($abstract); + + unset($this->aliases[$abstract]); + + // We'll check to determine if this type has been bound before, and if it has + // we will fire the rebound callbacks registered with the container and it + // can be updated with consuming classes that have gotten resolved here. + $this->instances[$abstract] = $instance; + + if ($isBound) { + $this->rebound($abstract); + } + + return $instance; + } + + /** + * Remove an alias from the contextual binding alias cache. + * + * @param string $searched + * @return void + */ + protected function removeAbstractAlias($searched) + { + if (! isset($this->aliases[$searched])) { + return; + } + + foreach ($this->abstractAliases as $abstract => $aliases) { + foreach ($aliases as $index => $alias) { + if ($alias == $searched) { + unset($this->abstractAliases[$abstract][$index]); + } + } + } + } + + /** + * Assign a set of tags to a given binding. + * + * @param array|string $abstracts + * @param array|mixed ...$tags + * @return void + */ + public function tag($abstracts, $tags) + { + $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1); + + foreach ($tags as $tag) { + if (! isset($this->tags[$tag])) { + $this->tags[$tag] = []; + } + + foreach ((array) $abstracts as $abstract) { + $this->tags[$tag][] = $abstract; + } + } + } + + /** + * Resolve all of the bindings for a given tag. + * + * @param string $tag + * @return iterable + */ + public function tagged($tag) + { + if (! isset($this->tags[$tag])) { + return []; + } + + return new RewindableGenerator(function () use ($tag) { + foreach ($this->tags[$tag] as $abstract) { + yield $this->make($abstract); + } + }, count($this->tags[$tag])); + } + + /** + * Alias a type to a different name. + * + * @param string $abstract + * @param string $alias + * @return void + * + * @throws \LogicException + */ + public function alias($abstract, $alias) + { + if ($alias === $abstract) { + throw new LogicException("[{$abstract}] is aliased to itself."); + } + + $this->aliases[$alias] = $abstract; + + $this->abstractAliases[$abstract][] = $alias; + } + + /** + * Bind a new callback to an abstract's rebind event. + * + * @param string $abstract + * @param \Closure $callback + * @return mixed + */ + public function rebinding($abstract, Closure $callback) + { + $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback; + + if ($this->bound($abstract)) { + return $this->make($abstract); + } + } + + /** + * Refresh an instance on the given target and method. + * + * @param string $abstract + * @param mixed $target + * @param string $method + * @return mixed + */ + public function refresh($abstract, $target, $method) + { + return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) { + $target->{$method}($instance); + }); + } + + /** + * Fire the "rebound" callbacks for the given abstract type. + * + * @param string $abstract + * @return void + */ + protected function rebound($abstract) + { + $instance = $this->make($abstract); + + foreach ($this->getReboundCallbacks($abstract) as $callback) { + $callback($this, $instance); + } + } + + /** + * Get the rebound callbacks for a given type. + * + * @param string $abstract + * @return array + */ + protected function getReboundCallbacks($abstract) + { + return $this->reboundCallbacks[$abstract] ?? []; + } + + /** + * Wrap the given closure such that its dependencies will be injected when executed. + * + * @param \Closure $callback + * @param array $parameters + * @return \Closure + */ + public function wrap(Closure $callback, array $parameters = []) + { + return fn () => $this->call($callback, $parameters); + } + + /** + * Call the given Closure / class@method and inject its dependencies. + * + * @param callable|string $callback + * @param array $parameters + * @param string|null $defaultMethod + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function call($callback, array $parameters = [], $defaultMethod = null) + { + $pushedToBuildStack = false; + + if (($className = $this->getClassForCallable($callback)) && ! in_array( + $className, + $this->buildStack, + true + )) { + $this->buildStack[] = $className; + + $pushedToBuildStack = true; + } + + $result = BoundMethod::call($this, $callback, $parameters, $defaultMethod); + + if ($pushedToBuildStack) { + array_pop($this->buildStack); + } + + return $result; + } + + /** + * Get the class name for the given callback, if one can be determined. + * + * @param callable|string $callback + * @return string|false + */ + protected function getClassForCallable($callback) + { + if (is_callable($callback) && + ! ($reflector = new ReflectionFunction($callback(...)))->isAnonymous()) { + return $reflector->getClosureScopeClass()->name ?? false; + } + + return false; + } + + /** + * Get a closure to resolve the given type from the container. + * + * @param string $abstract + * @return \Closure + */ + public function factory($abstract) + { + return fn () => $this->make($abstract); + } + + /** + * An alias function name for make(). + * + * @param string|callable $abstract + * @param array $parameters + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function makeWith($abstract, array $parameters = []) + { + return $this->make($abstract, $parameters); + } + + /** + * Resolve the given type from the container. + * + * @param string|callable $abstract + * @param array $parameters + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + public function make($abstract, array $parameters = []) + { + return $this->resolve($abstract, $parameters); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + public function get(string $id) + { + try { + return $this->resolve($id); + } catch (Exception $e) { + if ($this->has($id) || $e instanceof CircularDependencyException) { + throw $e; + } + + throw new EntryNotFoundException($id, is_int($e->getCode()) ? $e->getCode() : 0, $e); + } + } + + /** + * Resolve the given type from the container. + * + * @param string|callable $abstract + * @param array $parameters + * @param bool $raiseEvents + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + * @throws \Illuminate\Contracts\Container\CircularDependencyException + */ + protected function resolve($abstract, $parameters = [], $raiseEvents = true) + { + $abstract = $this->getAlias($abstract); + + // First we'll fire any event handlers which handle the "before" resolving of + // specific types. This gives some hooks the chance to add various extends + // calls to change the resolution of objects that they're interested in. + if ($raiseEvents) { + $this->fireBeforeResolvingCallbacks($abstract, $parameters); + } + + $concrete = $this->getContextualConcrete($abstract); + + $needsContextualBuild = ! empty($parameters) || ! is_null($concrete); + + // If an instance of the type is currently being managed as a singleton we'll + // just return an existing instance instead of instantiating new instances + // so the developer can keep using the same objects instance every time. + if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { + return $this->instances[$abstract]; + } + + $this->with[] = $parameters; + + if (is_null($concrete)) { + $concrete = $this->getConcrete($abstract); + } + + // We're ready to instantiate an instance of the concrete type registered for + // the binding. This will instantiate the types, as well as resolve any of + // its "nested" dependencies recursively until all have gotten resolved. + $object = $this->isBuildable($concrete, $abstract) + ? $this->build($concrete) + : $this->make($concrete); + + // If we defined any extenders for this type, we'll need to spin through them + // and apply them to the object being built. This allows for the extension + // of services, such as changing configuration or decorating the object. + foreach ($this->getExtenders($abstract) as $extender) { + $object = $extender($object, $this); + } + + // If the requested type is registered as a singleton we'll want to cache off + // the instances in "memory" so we can return it later without creating an + // entirely new instance of an object on each subsequent request for it. + if ($this->isShared($abstract) && ! $needsContextualBuild) { + $this->instances[$abstract] = $object; + } + + if ($raiseEvents) { + $this->fireResolvingCallbacks($abstract, $object); + } + + // Before returning, we will also set the resolved flag to "true" and pop off + // the parameter overrides for this build. After those two things are done + // we will be ready to return back the fully constructed class instance. + $this->resolved[$abstract] = true; + + array_pop($this->with); + + return $object; + } + + /** + * Get the concrete type for a given abstract. + * + * @param string|callable $abstract + * @return mixed + */ + protected function getConcrete($abstract) + { + // If we don't have a registered resolver or concrete for the type, we'll just + // assume each type is a concrete name and will attempt to resolve it as is + // since the container should be able to resolve concretes automatically. + if (isset($this->bindings[$abstract])) { + return $this->bindings[$abstract]['concrete']; + } + + return $abstract; + } + + /** + * Get the contextual concrete binding for the given abstract. + * + * @param string|callable $abstract + * @return \Closure|string|array|null + */ + protected function getContextualConcrete($abstract) + { + if (! is_null($binding = $this->findInContextualBindings($abstract))) { + return $binding; + } + + // Next we need to see if a contextual binding might be bound under an alias of the + // given abstract type. So, we will need to check if any aliases exist with this + // type and then spin through them and check for contextual bindings on these. + if (empty($this->abstractAliases[$abstract])) { + return; + } + + foreach ($this->abstractAliases[$abstract] as $alias) { + if (! is_null($binding = $this->findInContextualBindings($alias))) { + return $binding; + } + } + } + + /** + * Find the concrete binding for the given abstract in the contextual binding array. + * + * @param string|callable $abstract + * @return \Closure|string|null + */ + protected function findInContextualBindings($abstract) + { + return $this->contextual[end($this->buildStack)][$abstract] ?? null; + } + + /** + * Determine if the given concrete is buildable. + * + * @param mixed $concrete + * @param string $abstract + * @return bool + */ + protected function isBuildable($concrete, $abstract) + { + return $concrete === $abstract || $concrete instanceof Closure; + } + + /** + * Instantiate a concrete instance of the given type. + * + * @param \Closure|string $concrete + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + * @throws \Illuminate\Contracts\Container\CircularDependencyException + */ + public function build($concrete) + { + // If the concrete type is actually a Closure, we will just execute it and + // hand back the results of the functions, which allows functions to be + // used as resolvers for more fine-tuned resolution of these objects. + if ($concrete instanceof Closure) { + return $concrete($this, $this->getLastParameterOverride()); + } + + try { + $reflector = new ReflectionClass($concrete); + } catch (ReflectionException $e) { + throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e); + } + + // If the type is not instantiable, the developer is attempting to resolve + // an abstract type such as an Interface or Abstract Class and there is + // no binding registered for the abstractions so we need to bail out. + if (! $reflector->isInstantiable()) { + return $this->notInstantiable($concrete); + } + + $this->buildStack[] = $concrete; + + $constructor = $reflector->getConstructor(); + + // If there are no constructors, that means there are no dependencies then + // we can just resolve the instances of the objects right away, without + // resolving any other types or dependencies out of these containers. + if (is_null($constructor)) { + array_pop($this->buildStack); + + return new $concrete; + } + + $dependencies = $constructor->getParameters(); + + // Once we have all the constructor's parameters we can create each of the + // dependency instances and then use the reflection instances to make a + // new instance of this class, injecting the created dependencies in. + try { + $instances = $this->resolveDependencies($dependencies); + } catch (BindingResolutionException $e) { + array_pop($this->buildStack); + + throw $e; + } + + array_pop($this->buildStack); + + return $reflector->newInstanceArgs($instances); + } + + /** + * Resolve all of the dependencies from the ReflectionParameters. + * + * @param \ReflectionParameter[] $dependencies + * @return array + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolveDependencies(array $dependencies) + { + $results = []; + + foreach ($dependencies as $dependency) { + // If the dependency has an override for this particular build we will use + // that instead as the value. Otherwise, we will continue with this run + // of resolutions and let reflection attempt to determine the result. + if ($this->hasParameterOverride($dependency)) { + $results[] = $this->getParameterOverride($dependency); + + continue; + } + + // If the class is null, it means the dependency is a string or some other + // primitive type which we can not resolve since it is not a class and + // we will just bomb out with an error since we have no-where to go. + $result = is_null(Util::getParameterClassName($dependency)) + ? $this->resolvePrimitive($dependency) + : $this->resolveClass($dependency); + + if ($dependency->isVariadic()) { + $results = array_merge($results, $result); + } else { + $results[] = $result; + } + } + + return $results; + } + + /** + * Determine if the given dependency has a parameter override. + * + * @param \ReflectionParameter $dependency + * @return bool + */ + protected function hasParameterOverride($dependency) + { + return array_key_exists( + $dependency->name, $this->getLastParameterOverride() + ); + } + + /** + * Get a parameter override for a dependency. + * + * @param \ReflectionParameter $dependency + * @return mixed + */ + protected function getParameterOverride($dependency) + { + return $this->getLastParameterOverride()[$dependency->name]; + } + + /** + * Get the last parameter override. + * + * @return array + */ + protected function getLastParameterOverride() + { + return count($this->with) ? end($this->with) : []; + } + + /** + * Resolve a non-class hinted primitive dependency. + * + * @param \ReflectionParameter $parameter + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolvePrimitive(ReflectionParameter $parameter) + { + if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) { + return Util::unwrapIfClosure($concrete, $this); + } + + if ($parameter->isDefaultValueAvailable()) { + return $parameter->getDefaultValue(); + } + + if ($parameter->isVariadic()) { + return []; + } + + $this->unresolvablePrimitive($parameter); + } + + /** + * Resolve a class based dependency from the container. + * + * @param \ReflectionParameter $parameter + * @return mixed + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function resolveClass(ReflectionParameter $parameter) + { + try { + return $parameter->isVariadic() + ? $this->resolveVariadicClass($parameter) + : $this->make(Util::getParameterClassName($parameter)); + } + + // If we can not resolve the class instance, we will check to see if the value + // is optional, and if it is we will return the optional parameter value as + // the value of the dependency, similarly to how we do this with scalars. + catch (BindingResolutionException $e) { + if ($parameter->isDefaultValueAvailable()) { + array_pop($this->with); + + return $parameter->getDefaultValue(); + } + + if ($parameter->isVariadic()) { + array_pop($this->with); + + return []; + } + + throw $e; + } + } + + /** + * Resolve a class based variadic dependency from the container. + * + * @param \ReflectionParameter $parameter + * @return mixed + */ + protected function resolveVariadicClass(ReflectionParameter $parameter) + { + $className = Util::getParameterClassName($parameter); + + $abstract = $this->getAlias($className); + + if (! is_array($concrete = $this->getContextualConcrete($abstract))) { + return $this->make($className); + } + + return array_map(fn ($abstract) => $this->resolve($abstract), $concrete); + } + + /** + * Throw an exception that the concrete is not instantiable. + * + * @param string $concrete + * @return void + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function notInstantiable($concrete) + { + if (! empty($this->buildStack)) { + $previous = implode(', ', $this->buildStack); + + $message = "Target [$concrete] is not instantiable while building [$previous]."; + } else { + $message = "Target [$concrete] is not instantiable."; + } + + throw new BindingResolutionException($message); + } + + /** + * Throw an exception for an unresolvable primitive. + * + * @param \ReflectionParameter $parameter + * @return void + * + * @throws \Illuminate\Contracts\Container\BindingResolutionException + */ + protected function unresolvablePrimitive(ReflectionParameter $parameter) + { + $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}"; + + throw new BindingResolutionException($message); + } + + /** + * Register a new before resolving callback for all types. + * + * @param \Closure|string $abstract + * @param \Closure|null $callback + * @return void + */ + public function beforeResolving($abstract, Closure $callback = null) + { + if (is_string($abstract)) { + $abstract = $this->getAlias($abstract); + } + + if ($abstract instanceof Closure && is_null($callback)) { + $this->globalBeforeResolvingCallbacks[] = $abstract; + } else { + $this->beforeResolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Register a new resolving callback. + * + * @param \Closure|string $abstract + * @param \Closure|null $callback + * @return void + */ + public function resolving($abstract, Closure $callback = null) + { + if (is_string($abstract)) { + $abstract = $this->getAlias($abstract); + } + + if (is_null($callback) && $abstract instanceof Closure) { + $this->globalResolvingCallbacks[] = $abstract; + } else { + $this->resolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Register a new after resolving callback for all types. + * + * @param \Closure|string $abstract + * @param \Closure|null $callback + * @return void + */ + public function afterResolving($abstract, Closure $callback = null) + { + if (is_string($abstract)) { + $abstract = $this->getAlias($abstract); + } + + if ($abstract instanceof Closure && is_null($callback)) { + $this->globalAfterResolvingCallbacks[] = $abstract; + } else { + $this->afterResolvingCallbacks[$abstract][] = $callback; + } + } + + /** + * Fire all of the before resolving callbacks. + * + * @param string $abstract + * @param array $parameters + * @return void + */ + protected function fireBeforeResolvingCallbacks($abstract, $parameters = []) + { + $this->fireBeforeCallbackArray($abstract, $parameters, $this->globalBeforeResolvingCallbacks); + + foreach ($this->beforeResolvingCallbacks as $type => $callbacks) { + if ($type === $abstract || is_subclass_of($abstract, $type)) { + $this->fireBeforeCallbackArray($abstract, $parameters, $callbacks); + } + } + } + + /** + * Fire an array of callbacks with an object. + * + * @param string $abstract + * @param array $parameters + * @param array $callbacks + * @return void + */ + protected function fireBeforeCallbackArray($abstract, $parameters, array $callbacks) + { + foreach ($callbacks as $callback) { + $callback($abstract, $parameters, $this); + } + } + + /** + * Fire all of the resolving callbacks. + * + * @param string $abstract + * @param mixed $object + * @return void + */ + protected function fireResolvingCallbacks($abstract, $object) + { + $this->fireCallbackArray($object, $this->globalResolvingCallbacks); + + $this->fireCallbackArray( + $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks) + ); + + $this->fireAfterResolvingCallbacks($abstract, $object); + } + + /** + * Fire all of the after resolving callbacks. + * + * @param string $abstract + * @param mixed $object + * @return void + */ + protected function fireAfterResolvingCallbacks($abstract, $object) + { + $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks); + + $this->fireCallbackArray( + $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks) + ); + } + + /** + * Get all callbacks for a given type. + * + * @param string $abstract + * @param object $object + * @param array $callbacksPerType + * @return array + */ + protected function getCallbacksForType($abstract, $object, array $callbacksPerType) + { + $results = []; + + foreach ($callbacksPerType as $type => $callbacks) { + if ($type === $abstract || $object instanceof $type) { + $results = array_merge($results, $callbacks); + } + } + + return $results; + } + + /** + * Fire an array of callbacks with an object. + * + * @param mixed $object + * @param array $callbacks + * @return void + */ + protected function fireCallbackArray($object, array $callbacks) + { + foreach ($callbacks as $callback) { + $callback($object, $this); + } + } + + /** + * Get the container's bindings. + * + * @return array + */ + public function getBindings() + { + return $this->bindings; + } + + /** + * Get the alias for an abstract if available. + * + * @param string $abstract + * @return string + */ + public function getAlias($abstract) + { + return isset($this->aliases[$abstract]) + ? $this->getAlias($this->aliases[$abstract]) + : $abstract; + } + + /** + * Get the extender callbacks for a given type. + * + * @param string $abstract + * @return array + */ + protected function getExtenders($abstract) + { + return $this->extenders[$this->getAlias($abstract)] ?? []; + } + + /** + * Remove all of the extender callbacks for a given type. + * + * @param string $abstract + * @return void + */ + public function forgetExtenders($abstract) + { + unset($this->extenders[$this->getAlias($abstract)]); + } + + /** + * Drop all of the stale instances and aliases. + * + * @param string $abstract + * @return void + */ + protected function dropStaleInstances($abstract) + { + unset($this->instances[$abstract], $this->aliases[$abstract]); + } + + /** + * Remove a resolved instance from the instance cache. + * + * @param string $abstract + * @return void + */ + public function forgetInstance($abstract) + { + unset($this->instances[$abstract]); + } + + /** + * Clear all of the instances from the container. + * + * @return void + */ + public function forgetInstances() + { + $this->instances = []; + } + + /** + * Clear all of the scoped instances from the container. + * + * @return void + */ + public function forgetScopedInstances() + { + foreach ($this->scopedInstances as $scoped) { + unset($this->instances[$scoped]); + } + } + + /** + * Flush the container of all bindings and resolved instances. + * + * @return void + */ + public function flush() + { + $this->aliases = []; + $this->resolved = []; + $this->bindings = []; + $this->instances = []; + $this->abstractAliases = []; + $this->scopedInstances = []; + } + + /** + * Get the globally available instance of the container. + * + * @return static + */ + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + + return static::$instance; + } + + /** + * Set the shared instance of the container. + * + * @param \Illuminate\Contracts\Container\Container|null $container + * @return \Illuminate\Contracts\Container\Container|static + */ + public static function setInstance(ContainerContract $container = null) + { + return static::$instance = $container; + } + + /** + * Determine if a given offset exists. + * + * @param string $key + * @return bool + */ + public function offsetExists($key): bool + { + return $this->bound($key); + } + + /** + * Get the value at a given offset. + * + * @param string $key + * @return mixed + */ + public function offsetGet($key): mixed + { + return $this->make($key); + } + + /** + * Set the value at a given offset. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value): void + { + $this->bind($key, $value instanceof Closure ? $value : fn () => $value); + } + + /** + * Unset the value at a given offset. + * + * @param string $key + * @return void + */ + public function offsetUnset($key): void + { + unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]); + } + + /** + * Dynamically access container services. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this[$key]; + } + + /** + * Dynamically set container services. + * + * @param string $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + $this[$key] = $value; + } +} +``` \ No newline at end of file diff --git a/tests/test.md b/tests/test.md index 661d29c..2c891ca 100644 --- a/tests/test.md +++ b/tests/test.md @@ -1,25 +1,11 @@ -```js -// Declaration -class Rectangle { - constructor(height, width) { - this.height = height; - this.width = width; - } -} - -// Expression; the class is anonymous but assigned to a variable -const Rectangle = class { - constructor(height, width) { - this.height = height; - this.width = width; - } -}; - -// Expression; the class has its own name -const Rectangle = class Rectangle2 { - constructor(height, width) { - this.height = height; - this.width = width; - } -}; +```php + /** + * foo' + */ + + foo() . '' + + /** + * ' + */ ``` \ No newline at end of file