docs(docs-infra): lift circular imports (#63186)

This commit also adds adev on the cyclic import check.

PR Close #63186
This commit is contained in:
Matthieu Riegler
2025-08-15 15:07:02 +02:00
committed by Jessica Janiuk
parent cab3adfdd1
commit 9b539a18ba
20 changed files with 75 additions and 66 deletions
@@ -21,6 +21,7 @@ ng_project(
"//adev:node_modules/@angular/common",
"//adev:node_modules/@angular/core",
"//adev:node_modules/@angular/material",
"//adev:node_modules/@angular/platform-browser",
"//adev:node_modules/@angular/router",
"//adev:node_modules/rxjs",
"//adev/shared-docs/components/breadcrumb",
@@ -39,6 +39,7 @@ import {fromEvent} from 'rxjs';
import {Breadcrumb} from '../../breadcrumb/breadcrumb.component';
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
import {ExampleViewer} from '../example-viewer/example-viewer.component';
import {DomSanitizer} from '@angular/platform-browser';
const TOC_HOST_ELEMENT_NAME = 'docs-table-of-contents';
export const ASSETS_EXAMPLES_PATH = 'assets/content/examples';
@@ -74,6 +75,7 @@ export class DocViewer {
private readonly environmentInjector = inject(EnvironmentInjector);
private readonly injector = inject(Injector);
private readonly appRef = inject(ApplicationRef);
private readonly sanitizer = inject(DomSanitizer);
protected animateContent = false;
private readonly pendingTasks = inject(PendingTasks);
@@ -215,7 +217,7 @@ export class DocViewer {
return tabs.map((tab) => ({
name: tab.getAttribute('path') ?? tab.getAttribute('header') ?? '',
content: tab.innerHTML,
sanitizedContent: this.sanitizer.bypassSecurityTrustHtml(tab.innerHTML),
visibleLinesRange: tab.getAttribute('visibleLines') ?? undefined,
}));
}
@@ -235,7 +237,9 @@ export class DocViewer {
return {
title,
name: path,
content: content?.outerHTML,
sanitizedContent: content?.outerHTML
? this.sanitizer.bypassSecurityTrustHtml(content.outerHTML)
: '',
visibleLinesRange: visibleLines,
};
}
@@ -5,11 +5,7 @@
}
@if (view() === CodeExampleViewMode.MULTI_FILE) {
<mat-tab-group
#codeTabs
animationDuration="0ms"
mat-stretch-tabs="false"
>
<mat-tab-group #codeTabs animationDuration="0ms" mat-stretch-tabs="false">
@for (tab of tabs(); track tab) {
<mat-tab [label]="tab.name"></mat-tab>
}
@@ -87,7 +83,9 @@
[class.docs-example-viewer-multi-file]="view() === CodeExampleViewMode.MULTI_FILE"
>
<button docs-copy-source-code></button>
<docs-viewer [docContent]="snippetCode()?.content" />
@if (snippetCode()?.sanitizedContent; as content) {
<div [innerHTML]="content"></div>
}
</div>
@if (exampleComponent) {
@@ -53,9 +53,9 @@ describe('ExampleViewer', () => {
'metadata',
getMetadata({
files: [
{name: 'file.ts', content: ''},
{name: 'file.html', content: ''},
{name: 'file.css', content: ''},
{name: 'file.ts', sanitizedContent: ''},
{name: 'file.html', sanitizedContent: ''},
{name: 'file.css', sanitizedContent: ''},
],
}),
);
@@ -73,9 +73,9 @@ describe('ExampleViewer', () => {
'metadata',
getMetadata({
files: [
{name: 'file.ts', content: 'typescript file'},
{name: 'file.html', content: 'html file'},
{name: 'file.css', content: 'css file'},
{name: 'file.ts', sanitizedContent: 'typescript file'},
{name: 'file.html', sanitizedContent: 'html file'},
{name: 'file.css', sanitizedContent: 'css file'},
],
}),
);
@@ -93,9 +93,9 @@ describe('ExampleViewer', () => {
'metadata',
getMetadata({
files: [
{name: 'example.ts', content: 'typescript file'},
{name: 'example.html', content: 'html file'},
{name: 'another-example.ts', content: 'css file'},
{name: 'example.ts', sanitizedContent: 'typescript file'},
{name: 'example.html', sanitizedContent: 'html file'},
{name: 'another-example.ts', sanitizedContent: 'css file'},
],
}),
);
@@ -124,7 +124,7 @@ describe('ExampleViewer', () => {
files: [
{
name: 'example.ts',
content: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
sanitizedContent: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
visibleLinesRange: '[1]',
},
],
@@ -148,7 +148,7 @@ describe('ExampleViewer', () => {
files: [
{
name: 'example.ts',
content: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
sanitizedContent: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
visibleLinesRange: '[1]',
},
],
@@ -243,9 +243,9 @@ describe('ExampleViewer', () => {
files: [
{
name: 'example.ts',
content: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
sanitizedContent: `<pre><code>${expectedCodeSnippetContent}</code></pre>`,
},
{name: 'example.css', content: ''},
{name: 'example.css', sanitizedContent: ''},
],
}),
);
@@ -279,8 +279,8 @@ const getMetadata = (value: Partial<ExampleMetadata> = {}): ExampleMetadata => {
return {
id: 1,
files: [
{name: 'example.ts', content: ''},
{name: 'example.css', content: ''},
{name: 'example.ts', sanitizedContent: ''},
{name: 'example.css', sanitizedContent: ''},
],
preview: false,
...value,
@@ -14,7 +14,6 @@ import {
computed,
DestroyRef,
ElementRef,
forwardRef,
inject,
Injector,
input,
@@ -29,7 +28,6 @@ import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-co
import {ExampleMetadata, Snippet} from '../../../interfaces/index';
import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {DocViewer} from '../docs-viewer/docs-viewer.component';
export enum CodeExampleViewMode {
SNIPPET = 'snippet',
@@ -43,7 +41,7 @@ export const HIDDEN_CLASS_NAME = 'hidden';
@Component({
selector: 'docs-example-viewer',
imports: [CommonModule, forwardRef(() => DocViewer), CopySourceCodeButton, MatTabsModule],
imports: [CommonModule, CopySourceCodeButton, MatTabsModule],
templateUrl: './example-viewer.component.html',
styleUrls: ['./example-viewer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -80,7 +78,7 @@ export class ExampleViewer {
this.exampleMetadata()?.files.map((file) => ({
name:
file.title ?? (this.shouldDisplayFullName() ? file.name : this.getFileExtension(file.name)),
code: file.content,
code: file.sanitizedContent,
})),
);
view = computed(() =>
+1
View File
@@ -26,6 +26,7 @@ ts_project(
),
deps = [
"//adev:node_modules/@angular/core",
"//adev:node_modules/@angular/platform-browser",
"//adev:node_modules/@angular/router",
"//adev:node_modules/@types/node",
"//adev:node_modules/@webcontainer/api",
+2 -1
View File
@@ -7,6 +7,7 @@
*/
import {Type} from '@angular/core';
import {SafeHtml} from '@angular/platform-browser';
/**
* Map of the examples, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component
@@ -21,7 +22,7 @@ export interface Snippet {
/** Name of the file. */
name: string;
/** Content of code snippet */
content: string;
sanitizedContent: SafeHtml;
/** Text in following format `start-end`. Start and end are numbers, based on them provided range of lines will be displayed in collapsed mode */
visibleLinesRange?: string;
}
@@ -19,7 +19,7 @@ import {
import {EditorView} from '@codemirror/view';
import {Subject, filter, take} from 'rxjs';
import {EditorFile} from '../code-mirror-editor.service';
import type {EditorFile} from '../code-mirror-editor.service';
import {TsVfsWorkerActions} from '../workers/enums/actions';
import {AutocompleteRequest} from '../workers/interfaces/autocomplete-request';
import {AutocompleteItem, AutocompleteResponse} from '../workers/interfaces/autocomplete-response';
@@ -9,7 +9,7 @@
import {Diagnostic, linter} from '@codemirror/lint';
import {TsVfsWorkerActions} from '../workers/enums/actions';
import {Signal} from '@angular/core';
import {EditorFile} from '../code-mirror-editor.service';
import type {EditorFile} from '../code-mirror-editor.service';
import {ActionMessage} from '../workers/interfaces/message';
import {DiagnosticsRequest} from '../workers/interfaces/diagnostics-request';
import {Subject, filter, take} from 'rxjs';
@@ -13,7 +13,7 @@ import {Subject, filter, take} from 'rxjs';
import ts from 'typescript';
import {EditorFile} from '../code-mirror-editor.service';
import type {EditorFile} from '../code-mirror-editor.service';
import {TsVfsWorkerActions} from '../workers/enums/actions';
import {DisplayTooltipRequest} from '../workers/interfaces/display-tooltip-request';
import {DisplayTooltipResponse} from '../workers/interfaces/display-tooltip-response';
@@ -0,0 +1,10 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
export const DEV_SERVER_READY_MSG = 'Watch mode enabled. Watching for file changes...';
export const OUT_OF_MEMORY_MSG = 'Out of memory';
@@ -15,15 +15,11 @@ import {FakeWebContainer, FakeWebContainerProcess, TutorialType} from '@angular/
import {AlertManager} from './alert-manager.service';
import {EmbeddedTutorialManager} from './embedded-tutorial-manager.service';
import {LoadingStep} from './enums/loading-steps';
import {
DEV_SERVER_READY_MSG,
NodeRuntimeSandbox,
OUT_OF_MEMORY_MSG,
PACKAGE_MANAGER,
} from './node-runtime-sandbox.service';
import {NodeRuntimeSandbox, PACKAGE_MANAGER} from './node-runtime-sandbox.service';
import {NodeRuntimeState} from './node-runtime-state.service';
import {TerminalHandler} from './terminal/terminal-handler.service';
import {TypingsLoader} from './typings-loader.service';
import {DEV_SERVER_READY_MSG, OUT_OF_MEMORY_MSG} from './node-runtime-errors';
describe('NodeRuntimeSandbox', () => {
let testBed: TestBed;
@@ -19,9 +19,7 @@ import {LoadingStep} from './enums/loading-steps';
import {ErrorType, NodeRuntimeState} from './node-runtime-state.service';
import {TerminalHandler} from './terminal/terminal-handler.service';
import {TypingsLoader} from './typings-loader.service';
export const DEV_SERVER_READY_MSG = 'Watch mode enabled. Watching for file changes...';
export const OUT_OF_MEMORY_MSG = 'Out of memory';
import {DEV_SERVER_READY_MSG, OUT_OF_MEMORY_MSG} from './node-runtime-errors';
const enum PROCESS_EXIT_CODE {
SUCCESS = 0, // process exited successfully
@@ -9,7 +9,7 @@
import {TestBed} from '@angular/core/testing';
import {ErrorType, NodeRuntimeState} from './node-runtime-state.service';
import {OUT_OF_MEMORY_MSG} from './node-runtime-sandbox.service';
import {OUT_OF_MEMORY_MSG} from './node-runtime-errors';
describe('NodeRuntimeState', () => {
let service: NodeRuntimeState;
@@ -7,10 +7,10 @@
*/
import {Injectable, signal} from '@angular/core';
import {isFirefox, isIos} from '@angular/docs';
import {isIos} from '@angular/docs';
import {LoadingStep} from './enums/loading-steps';
import {OUT_OF_MEMORY_MSG} from './node-runtime-sandbox.service';
import {OUT_OF_MEMORY_MSG} from './node-runtime-errors';
export const MAX_RECOMMENDED_WEBCONTAINERS_INSTANCES = 3;
export const WEBCONTAINERS_COUNTER_KEY = 'numberOfWebcontainers';
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Animation} from '../animation';
import type {Animation} from '../animation';
/**
* Animation plugin interface.
@@ -1,10 +0,0 @@
import {Component, inject} from '@angular/core';
import {ParentComponent} from './parent.component';
@Component({
selector: 'app-child',
template: 'The child!',
})
export class ChildComponent {
private parent = inject(ParentComponent);
}
@@ -1,9 +0,0 @@
import {Component} from '@angular/core';
import {ChildComponent} from './child.component';
@Component({
selector: 'app-parent',
imports: [ChildComponent],
template: '<app-child/>',
})
export class ParentComponent {}
+23 -2
View File
@@ -3,9 +3,30 @@
A component, directive, or pipe that is referenced by this component would require the compiler to add an import that would lead to a cycle of imports.
For example, consider a scenario where a `ParentComponent` references a `ChildComponent` in its template:
<docs-code header="parent.component.ts" path="adev/src/content/examples/errors/cyclic-imports/parent.component.ts"/>
<docs-code header="parent.component.ts" language="angular-ts">
import {Component} from '@angular/core';
import {ChildComponent} from './child.component';
<docs-code header="child.component.ts" path="adev/src/content/examples/errors/cyclic-imports/child.component.ts"/>
@Component({
selector: 'app-parent',
imports: [ChildComponent],
template: '<app-child/>',
})
export class ParentComponent {}
</docs-code>
<docs-code header="child.component.ts" language="angular-ts">
import {Component, inject} from '@angular/core';
import {ParentComponent} from './parent.component';
@Component({
selector: 'app-child',
template: 'The child!',
})
export class ChildComponent {
private parent = inject(ParentComponent);
}
</docs-code>
There is already an import from `child.component.ts` to `parent.component.ts` since the `ChildComponent` references the `ParentComponent` in its constructor.
+1 -1
View File
@@ -10,7 +10,7 @@ const path = require('path');
module.exports = {
baseDir: '../',
glob: `./**/*.ts`,
glob: `../{packages,adev}/**/*.ts`,
resolveModule: resolveModule,
ignoreTypeOnlyChecks: true,
};