618 Commits

Author SHA1 Message Date
AleksanderBodurri 54ea3b65c3 fix(core): emit provider configured event when a service is configured with providedIn (#52365)
Previously this case was missed by the default framework injector profiler. Now in ngDevMode this event emits correctly when a service is configured with `providedIn`. This includes the case where injection tokens are configured with a `providedIn`.

This commit also includes unit tests for this new case in the injector profiler.

PR Close #52365
2023-10-25 10:51:21 -07:00
Andrew Scott 1693846a9b test(core): fix test observing broken OnPush checkNoChanges (#52337)
The test is actually observing that OnPush results in a view not being checked
when checkNoChanges runs.

PR Close #52337
2023-10-24 09:21:52 -07:00
JoostK 82712f80df fix(core): ensure a consumer drops all its stale producers (#51722) (#51772)
When a producer is no longer used, the consumer has to update its internal data structure
that keeps track of all producers. There used to be an issue where only half of the stale
producers would actually be removed from this data structure, as the intended upper bound
of the number of producers to remove would decrease with each removed producer, therefore
not reaching all producers that should be removed from the data structure.

This commit fixes the issue by truncating the arrays directly, without going through
individual `pop` operations. An assertion that would catch the inconsistent state in
the internal data structures of the signal graph has been introduced.

PR Close #51722

PR Close #51772
2023-09-14 12:51:30 +02:00
Gerald Monaco c700418ab8 refactor(core): delegate afterRender errors to an ErrorHandler (#51711)
Improves the error handling story for after*Render by delegating errors to an ErrorHandler, so that one failure does not break every callback.

PR Close #51711
2023-09-08 14:21:06 -07:00
Joey Perrott e6b301caa2 fix(core): remove unnecessary escaping in regex expressions (#51554)
Correct various Useless regular-expression character escape issues.

PR Close #51554
2023-08-29 21:52:34 +00:00
Gerald Monaco 0c7c852ee7 fix(core): run afterRender callbacks outside of the Angular zone (#51551)
afterRender should run outside of the Angular zone so that it does not trigger further CD cycles

PR Close #51551
2023-08-29 17:54:20 +00:00
Matthieu Riegler 26781fd0fe refactor(core): remove unnecessary import for custom expect. (#51216)
Also adding some missing assertion after `expect()`

PR Close #51216
2023-08-07 09:22:20 -07:00
Gerald Monaco e53d4ecf4c feat(core): add afterRender and afterNextRender (#50607)
Add and expose the after*Render functions as developer preview

PR Close #50607
2023-08-01 13:02:27 -07:00
AleksanderBodurri c1dee4cfe3 test(core): unit tests for the injector profiler and injector debugging APIs (#48639)
Creates unit tests for the following APIs

    - setInjectorProfiler
    - getInjectorProviders
    - getInjectorResolutionPath
    - getDependenciesFromInjectable

    Modifies existing tests in

    - packages/examples/core/di/ts/injector_spec.ts
    - packages/core/test/render3/jit/declare_injectable_spec.ts
    - packages/core/test/render3/jit/declare_factory_spec.ts

    because they setup framework injector context manually.

    Exports setInjectorProfilerContext in packages/core/src/core_private_export.ts in order for use
    in the the modified tests above.

PR Close #48639
2023-08-01 11:44:40 -07:00
Andrew Kushnir 031b599a55 fix(core): ensure that standalone components get correct injector instances (#50954)
Prior to this change, we've used `componentDef.id` as a key in a Map that acts as a cache to avoid re-creating injector instances for standalone components. In v16, the logic that generates the id has changed from an auto-incremental to a generation based on metadata. If multiple components have similar metadata, their ids might overlap.

This commit updates the logic to stop using `componentDef.id` as a key and instead, use the `componentDef` itself. This would ensure that we always have a correct instance of an injector associated with a standalone component instance.

Resolves #50724.

PR Close #50954
2023-07-10 07:07:44 -07:00
Kristiyan Kostadinov 29340a0678 fix(core): expose input transform function on ComponentFactory and ComponentMirror (#50713)
Exposes the function used to transform an input on `ComponentFactory.inputs` and `ComponentMirror.inputs`. We'll need this to support input transforms in `elements`.

PR Close #50713
2023-06-22 09:47:15 -07:00
Matthieu Riegler 8d42747971 refactor(core): handle #24571 todos. (#49221)
This commit removes the remaining TODO(issue/24571) in core code base.

PR Close #49221
2023-06-14 12:33:44 +02:00
Matthieu Riegler ad28cddd41 refactor(platform-browser): replace our own toBeAnInstanceOf with toBeInstanceOf (#50661)
There is no need to maintain that matcher since jasmine provides its own !

PR Close #50661
2023-06-14 10:58:04 +02:00
Kristiyan Kostadinov 79a706c847 fix(core): incorrectly throwing error for self-referencing component (#50559)
Components are implied to be self-referencing, but if they explicitly set themselves in the `imports` array, they would throw an error because we weren't filtering them out.

Fixes #50525.

PR Close #50559
2023-06-06 14:23:40 -07:00
Kristiyan Kostadinov 68017d4e75 feat(core): add ability to transform input values (#50420)
According to the HTML specification most attributes are defined as strings, however some can be interpreted as different types like booleans or numbers. [In the HTML standard](https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#boolean-attributes), boolean attributes are considered `true` if they are present on a DOM node and `false` if they are omitted. Common examples of boolean attributes are `disabled` on interactive elements like `<button>` or `checked` on `<input type="checkbox">`. Another example of an attribute that is defined as a string, but interpreted as a different type is the `value` attribute of `<input type="number">` which logs a warning and ignores the value if it can't be parsed as a number.

Historically, authoring Angular inputs that match the native behavior in a type-safe way has been difficult for developers, because Angular interprets all static attributes as strings. While some recent TypeScript versions made this easier by allowing setters and getters to have different types, supporting this pattern still requires a lot of boilerplate and additional properties to be declared. For example, currently developers have to write something like this to have a `disabled` input that behaves like the native one:

```typescript
import {Directive, Input} from '@angular/core';

@Directive({selector: 'mat-checkbox'})
export class MatCheckbox {
  @Input()
  get disabled() {
    return this._disabled;
  }
  set disabled(value: any) {
    this._disabled = typeof value === 'boolean' ? value : (value != null && value !== 'false');
  }
  private _disabled = false;
}
```

This feature aims to address the issue by introducing a `transform` property on inputs. If an input has a `transform` function, any values set through the template will be passed through the function before being assigned to the directive instance. The example from above can be rewritten to the following:

```typescript
import {Directive, Input, booleanAttribute} from '@angular/core';

@Directive({selector: 'mat-checkbox'})
export class MatCheckbox {
  @Input({transform: booleanAttribute}) disabled: boolean = false;
}
```

These changes also add the `booleanAttribute` and `numberAttribute` utilities to `@angular/core` since they're common enough to be useful for most projects.

Fixes #8968.
Fixes #14761.

PR Close #50420
2023-05-30 13:01:13 -07:00
Alan Agius 28c68f709c fix(core): update ApplicationRef.isStable to account for rendering pending tasks (#50425)
This commit updates the `ApplicationRef.isStable` API to account for
pending rendering task. This is needed as once a pending rendering task
is done, new macrotask and microtask could be created which previously caused these not
to be intercepted and thus ignored when doing SSR.

PR Close #50425
2023-05-30 12:58:22 -07:00
gdarnell 75fdbcb8f2 fix(core): fix Self flag inside embedded views with custom injectors (#50270)
When an embedded view injector is present anywhere above a node in the tree, the `Self` flag was effectively ignored. With this change, embedded view injectors are not checked at all when the `Self` flag is present, because resolution should stop at the current node before reaching any embedded view injector(s).

Fixes #49959

PR Close #50270
2023-05-24 13:59:20 +00:00
BrianDGLS 2d411430e3 refactor(router): run spell check on router package (#50445)
Fix typos in router package.

PR Close #50445
2023-05-24 13:56:56 +00:00
Kristiyan Kostadinov 8b44ba3170 fix(core): host directives incorrectly validating aliased bindings (#50364)
Fixes that the host directives feature was incorrectly throwing the conflicting alias error when an aliased binding was being exposed under the same alias.

Fixes #48951.

PR Close #50364
2023-05-19 15:09:48 +00:00
Matthieu Riegler 7c790f70ee refactor(core): Improve ExpressionChangedAfterItHasBeenCheckedError (#50286)
Related to #50272 and #18970, this improves the error message of NG100 by including the class name of the component where the error was triggered.

PR Close #50286
2023-05-16 09:24:51 -07:00
gdarnell 03f1e240b3 fix(core): allow onDestroy unregistration while destroying (#50237)
Ensure that all onDestroy callbacks are called by allowing unregistering of onDestroy hooks while destroying.

Fixes #50221

PR Close #50237
2023-05-11 10:17:07 -07:00
Angular Robot 84a2e7db55 build: lock file maintenance (#49914)
See associated pull request for more information.

PR Close #49914
2023-05-09 14:38:45 -07:00
Pawel Kozlowski 1dc919a3df fix(core): execute query setters in non-reactive context (#49906)
This commit assures that query results setters run when there is no active
reactive consumer set.

PR Close #49906
2023-04-19 18:12:15 +00:00
Pawel Kozlowski 40318021ee fix(core): execute input setters in non-reactive context (#49906)
This change explicitly resets a reactive consumer before setting inputs
on directive instances. This is to assure that any potential input setters
do _not_ run in the reactive context.

PR Close #49906
2023-04-19 18:12:15 +00:00
Kristiyan Kostadinov 9165942629 fix(core): handle invalid classes in class array bindings (#49924)
When binding an array to `class` like `[class]="['foo', 'bar']"`, the runtime treats it the same as a literal binding with all the values being `true`, e.g. `{foo: true, bar: true}`. While object literals can only have string keys, arrays can have any value which can lead to errors if the array contains non-string values.

These changes add some logic to stringify the keys and ignore invalid ones.

Fixes #48473.

PR Close #49924
2023-04-19 16:28:26 +00:00
Pawel Kozlowski b7392f9064 fix(core): execute template creation in non-reactive context (#49883)
This fix assures that templates functions executed in the creation mode
are run outside of the reactive context. This avoids the situation where
signal reads in a directive constructor (executed as part of the creation
mode) would mark the host component as dirty.

Fixes #49871

PR Close #49883
2023-04-18 13:59:42 +00:00
Matthieu Riegler f3366c60e1 refactor(core): improve styling coverage (#49868)
The test was waiting for #34202 to be merged.

PR Close #49868
2023-04-17 14:02:40 +00:00
Andrew Kushnir d994f8520c fix(core): include inner ViewContainerRef anchor nodes into ViewRef.rootNodes output (#49867)
Currently, the `ViewRef.rootNodes` output is missing anchor (comment) nodes for inner `ViewContainerRef`s,
when an achor node was created for that instance of a `ViewContainerRef` (which happens in all cases except
when an <ng-container> was used as a host for a view container).

This issue affects hydration logic, which relies on the number of root nodes within a view to properly determine
segments in DOM that belong to a particular view.

Resolves #49849.

PR Close #49867
2023-04-17 13:35:58 +00:00
Andrew Scott a26e6f6fa8 test(core): re-add tests for signals in OnPush components (#49836)
These tests were accidentally deleted during a rebase.

PR Close #49836
2023-04-14 14:12:23 +00:00
Pawel Kozlowski 2c22e6fb5f fix(core): onDestroy should be registered only on valid DestroyRef (#49804)
It might happen that the lifecycle scope represented by DestroyRef becomes
invalid before an onDestroy hook is registered (ex. injector or component
instance got destroyed). In such cases registration of the onDestroy hooks
should no longer be possible.

Fixes #49658

PR Close #49804
2023-04-13 21:14:44 +00:00
Matthieu Riegler 38fe1b91fc refactor(core): drop IE workarounds (#49763)
Angular doesn't support IE anymore. We can remove the workarounds related to IE.

Some workarounds are keep because of the support of domino but the comments related to IE are removed.

PR Close #49763
2023-04-13 14:01:45 +00:00
Alan Agius 42f2f41f9e refactor(platform-browser): update renderer to be able to remove ServerRendererFactory2 and EmulatedEncapsulationServerRenderer2 (#49630)
This commits updates the render to able to handle the slight differences between platform-server and platform-browser.

This is needed to eventually be able to remove `ServerRendererFactory2` and `EmulatedEncapsulationServerRenderer2` from platform-server.

PR Close #49630
2023-04-05 11:42:57 -07:00
Matthieu Riegler 39a648f31c refactor(core): Use (where applicable), ViewContainer.createComponent instead of relying on the ComponentFactoryResolver. (#49614)
`ComponentFactoryResolver` has been deprecated for public usage since its not necessary anymore. Lets reflect that in the unit tests.

PR Close #49614
2023-03-30 11:43:35 -07:00
Alan Agius e8e36811d5 fix(platform-browser): set nonce attribute in a platform compatible way (#49624)
Setting the `nonce` attribute using the property is not supported by Domino. This change update the usage to use `setAttribute` and also add a test to verify that the `nonce` is set when it should.

PR Close #49624
2023-03-29 09:26:54 -07:00
Alex Rickabaugh 9e40250d27 refactor(core): unify signal producer/consumer into nodes (#49529)
Previously the signals implementation maintained separate interfaces for
`Producer` and `Consumer` nodes, with implementers choosing to implement
one or both interfaces. Operations defined against those interfaces were
exposed as importable functions to be called with the object implementing
the relevant contract as the first argument.

This commit refactors the implementation to merge both abstractions into a
single `ReactiveNode` base class, which represents both producers and
consumers in the graph. Implementers choose to interact with a subset of the
`ReactiveNode` API depending on their role in the graph. Operations are now
available as protected methods on the base class, instead of separate
functions.

PR Close #49529
2023-03-28 18:47:58 -07:00
Alex Rickabaugh 89d291c367 feat(core): add assertInInjectionContext (#49529)
This commit adds an assertion function to the public API, which allows
authors of functions which rely on `inject` to validate that they're being
called with the right context. This mostly produces a nicer error message
than calling `inject()` and relying on Angular's default error message for
that.

PR Close #49529
2023-03-28 18:47:58 -07:00
Andrew Kushnir 478c5ac150 refactor(core): internal tracker of pending tasks during initial rendering (#49576)
This commit implements a simple tracker of the pending tasks during initial rendering. The class allows adding and removing tasks from the set. The class also exposes a promise that gets resolved once the last task is removed.

This tracker is needed to keep track of ongoing processes like Router navigation (and potentially HTTP requests) and acts as a signaling mechanism to SSR and hydration that the application is in the "stable" state and a serialization can be performed.

This class would also act as a future replacement for the `ApplicationRef.isStable` for zoneless applications.

PR Close #49576
2023-03-28 16:19:31 -07:00
Alan Agius 1829542aea fix(compiler): do not unquote CSS values (#49460)
Currently we are unsafely unquoting CSS values which in some cases causes valid values to become invalid and invalid values to become valid.

Example:
```html
<div style="width:&quot;1px;&quot;"></div>
```

In the above case, `width` has an invalid value of `"1px"`, however the compiler will transform it to `1px` which makes it valid.

On the other hand,  in the below case
```html
<div style="content:&quot;foo&quot;"></div>
```

`content` has a valid value of `"foo"`, but since the compiler unwraps it to `foo` it becomes invalid. For correctness, we should not remove quotes.

```js
const div = document.createElement('div');
div.style.width='"1px"';
div.style.content='foo';

div.style.width; // ''
div.style.content; // ''

div.style.width='1px';
div.style.content='"foo"';

div.style.width; // '1px'
div.style.content; // '"foo"'
```

More information about values can be found https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier

PR Close #49460
2023-03-28 11:35:38 -07:00
Matthieu Riegler 74adf81446 refactor(core): Remove usage of deprecated Injector.create() (#49606)
This commit removes all usages of the deprecated `Injector.create` method but does not remove the deprecated signature itself.

PR Close #49606
2023-03-28 10:15:35 -07:00
Andrew Scott 9b65b84cb9 feat(core): Mark components for check if they read a signal (#49153)
This commit updates the `LView` in Angular to be a `Consumer` of
signals. If a signal is read when executing a template, it marks the
view dirty. In addition, if a signal is read when executing host
bindings, it also marks views dirty.

One interesting thing about signal reads in host bindings
is that they perform a bit better than what we can do with today's
APIs. In order to re-execute host bindings for an `OnPush` component that
might have changed, you would probably inject `ChangeDetectorRef` and call
`markForCheck`. This will mark the _current component_ and parents
dirty. However, host bindings are executed as part of refreshing the
_parent_ so there is really no need to re-execute the current component
if the only thing that changed is the host bindings. When a signal is
read in host bindings, it marks the parent dirty and not the component
that defined the host binding.

Additionally, this commit avoids allocating a full consumer for each
`LView` by re-using a consumer until template execution results in a
signal read. At this point, we assign that consumer to the `LView` and
create a new consumer to "tentatively" use for the future `LView`
template executions.

Co-authored-by: Dylan Hunn <github@dylanhunn.com>

PR Close #49153
2023-03-27 11:19:06 -07:00
Kristiyan Kostadinov 585e34bf6c feat(core): remove entryComponents (#49484)
`entryComponents` have been deprecated since version 9, because with Ivy they weren't necessary. These changes remove any remaining references.

BREAKING CHANGE:
* `entryComponents` has been deleted from the `@NgModule` and `@Component` public APIs. Any usages can be removed since they weren't doing anyting.
* `ANALYZE_FOR_ENTRY_COMPONENTS` injection token has been deleted. Any references can be removed.

PR Close #49484
2023-03-23 10:38:03 -07:00
Pawel Kozlowski d1617c449d feat(core): allow removal of previously registered DestroyRef callbacks (#49493)
This change makes it possible to remove a previously registered destroy
callback - to do so it is enough to call the unregistration function
returned from the onDestroy method call.

PR Close #49493
2023-03-21 16:58:22 +01:00
Masaoki Kobayashi e655e8a603 fix(core): more accurate matching of classes during content projection (#48888)
Showing a minimum app to reproduce the bug.
1. Create the app and add angular material.
```
ng new project
cd project
ng add @angular/material
```
1. Overwrite the src/app/app.component.html with minimal content.
```
<button mat-button *ngIf="true"><span *ngIf="true" class="{{'a'}}"></span></button>
```
1. Run the app. The button is not shown because of an exception.
```
main.ts:6
ERROR TypeError: item.toLowerCase is not a function
    at isCssClassMatching (core.mjs:8726:35)
    at isNodeMatchingSelector (core.mjs:8814:22)
    at isNodeMatchingSelectorList (core.mjs:8931:13)
    at matchingProjectionSlotIndex (core.mjs:14179:13)
    at Module.ɵɵprojectionDef (core.mjs:14222:49)
    at MatButton_Template (button.mjs:113:99)
    at executeTemplate (core.mjs:10534:9)
    at renderView (core.mjs:10356:13)
    at renderComponent (core.mjs:11529:5)
    at renderChildComponents (core.mjs:10216:9)
```
Because isCssClassMatching() function does not take care if the value is not string, while attrs[] may contain AttributeMarker which is actually numbers, item.toLowerCase() throws the exception.
Just inserted a check if the item is string.

Created a testcase for the original fix. It causes an exception without the fix.

fix(core): add a check code to avoid an exception inside isCssClassMatching

Showing a minimum app to reproduce the bug.
1. Create the app and add angular material.
```
ng new project
cd project
ng add @angular/material
```
1. Add `import { MatButtonModule } from '@angular/material/button'`,
   and also MatButtonModule inside @NgModule imports in src/app/app.module.ts to use MatButtonModule.
1. Overwrite the src/app/app.component.html with minimal content.
```
<button mat-button *ngIf="true"><span *ngIf="true" class="{{'a'}}"></span></button>
```
1. Run the app. The button is not shown because of an exception.
```
main.ts:6
ERROR TypeError: item.toLowerCase is not a function
    at isCssClassMatching (core.mjs:8726:35)
    at isNodeMatchingSelector (core.mjs:8814:22)
    at isNodeMatchingSelectorList (core.mjs:8931:13)
    at matchingProjectionSlotIndex (core.mjs:14179:13)
    at Module.ɵɵprojectionDef (core.mjs:14222:49)
    at MatButton_Template (button.mjs:113:99)
    at executeTemplate (core.mjs:10534:9)
    at renderView (core.mjs:10356:13)
    at renderComponent (core.mjs:11529:5)
    at renderChildComponents (core.mjs:10216:9)
```
Because isCssClassMatching() function does not take care if the value is not string, while attrs[] may contain AttributeMarker which is actually numbers, item.toLowerCase() throws the exception.
Just inserted a check if the item is string.

PR Close #48888
2023-03-20 16:07:13 +01:00
Kristiyan Kostadinov 8f539c11f4 feat(compiler): add support for compile-time required inputs (#49468)
Adds support for marking a directive input as required. During template type checking, the compiler will verify that all required inputs have been specified and will raise a diagnostic if one or more are missing. Some specifics:
* Inputs are marked as required by passing an object literal with a `required: true` property to the `Input` decorator or into the `inputs` array.
* Required inputs imply that the directive can't work without them. This is why there's a new check that enforces that all required inputs of a host directive are exposed on the host.
* Required input diagnostics are reported through the `OutOfBandDiagnosticRecorder`, rather than generating a new structure in the TCB, because it allows us to provide a better error message.
* Currently required inputs are only supported during AOT compilation, because knowing which bindings are present during JIT can be tricky and may lead to increased bundle sizes.

Fixes #37706.

PR Close #49468
2023-03-20 13:10:30 +01:00
Matthieu Riegler 230345876c fix(core): Allow TestBed.configureTestingModule to work with recursive cycle of standalone components. (#49473)
When having a recursive circle of imports on standalone components, `queueTypesFromModulesArrayRecur` triggered a `Maximum call stack size exceeded` error.
This commit fixes this.

Fixes #49469

PR Close #49473
2023-03-20 10:02:15 +01:00
Andrew Scott 8d99ad0a39 Revert "feat(compiler): add support for compile-time required inputs (#49453)" (#49467)
This reverts commit 13dd614cd1.

This breaks a g3 Typescript compilation tests where diagnostics are
expected for a missing input in the component.

PR Close #49467
2023-03-17 18:29:14 +01:00
Kristiyan Kostadinov 17e9862653 feat(core): add API to provide CSP nonce for inline stylesheets (#49444)
Angular uses inline styles to insert the styles associated with a component. This violates the strict styles [Content Security Policy](https://web.dev/strict-csp/) which doesn't allow inline styles by default. One way to allow the styles to be applied is to set a `nonce` attribute on them, but because the code for inserting the stylesheets is deep inside the framework, users weren't able to provide it without accessing private APIs.

These changes add a new `CSP_NONCE` injection token that will allow users to provide a nonce, if their app is using CSP. If the token isn't provided, the framework will look for an `ngCspNonce` attribute on the app's root node instead. The latter approach is provided as a convenience for apps that render the `index.html` through a server, e.g. `<app ngCspNonce="{% randomNonceAddedByTheServer %}"></app>`.

This PR addresses adding the nonce to framework-generated styles. There will be follow-up PRs that add support for it in critical CSS tags in the CLI, and in Angular Material.

Fixes #6361.

PR Close #49444
2023-03-17 14:08:21 +01:00
Kristiyan Kostadinov 13dd614cd1 feat(compiler): add support for compile-time required inputs (#49453)
Adds support for marking a directive input as required. During template type checking, the compiler will verify that all required inputs have been specified and will raise a diagnostic if one or more are missing. Some specifics:
* Inputs are marked as required by passing an object literal with a `required: true` property to the `Input` decorator or into the `inputs` array.
* Required inputs imply that the directive can't work without them. This is why there's a new check that enforces that all required inputs of a host directive are exposed on the host.
* Required input diagnostics are reported through the `OutOfBandDiagnosticRecorder`, rather than generating a new structure in the TCB, because it allows us to provide a better error message.
* Currently required inputs are only supported during AOT compilation, because knowing which bindings are present during JIT can be tricky and may lead to increased bundle sizes.

Fixes #37706.

PR Close #49453
2023-03-17 11:49:17 +01:00
Alan Agius 9ab9a2a817 refactor(platform-browser): combine DomSharedStylesHost and SharedStylesHost (#49424)
The mentioned 2 classes have been combined since it is no longer required to have a separate `SharedStylesHost` for SSR. This changes also reduces the memory usage footprint as remove 1 Map that stores the CSS strings.

PR Close #49424
2023-03-17 10:46:03 +01:00
Alex Rickabaugh 560b226a43 Revert "feat(compiler): add support for compile-time required inputs (#49304)" (#49449)
This reverts commit 1a6ca68154.

This breaks tests in google3 which might be depending on private APIs. We
need to update these tests before we can land this PR.

PR Close #49449
2023-03-16 10:38:04 -07:00