This is page 2 of 10. Use http://codebase.md/push-based/angular-toolkit-mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .aiignore ├── .cursor │ ├── flows │ │ ├── component-refactoring │ │ │ ├── 01-review-component.mdc │ │ │ ├── 02-refactor-component.mdc │ │ │ ├── 03-validate-component.mdc │ │ │ └── angular-20.md │ │ ├── ds-refactoring-flow │ │ │ ├── 01-find-violations.mdc │ │ │ ├── 01b-find-all-violations.mdc │ │ │ ├── 02-plan-refactoring.mdc │ │ │ ├── 02b-plan-refactoring-for-all-violations.mdc │ │ │ ├── 03-fix-violations.mdc │ │ │ ├── 03-non-viable-cases.mdc │ │ │ ├── 04-validate-changes.mdc │ │ │ ├── 05-prepare-report.mdc │ │ │ └── clean-global-styles.mdc │ │ └── README.md │ └── mcp.json.example ├── .github │ └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── assets │ ├── entain-logo.png │ └── entain.png ├── CONTRIBUTING.MD ├── docs │ ├── architecture-internal-design.md │ ├── component-refactoring-flow.md │ ├── contracts.md │ ├── ds-refactoring-flow.md │ ├── getting-started.md │ ├── README.md │ ├── tools.md │ └── writing-custom-tools.md ├── eslint.config.mjs ├── jest.config.ts ├── jest.preset.mjs ├── LICENSE ├── nx.json ├── package-lock.json ├── package.json ├── packages │ ├── .gitkeep │ ├── angular-mcp │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── assets │ │ │ │ └── .gitkeep │ │ │ └── main.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── vitest.config.mts │ │ └── webpack.config.cjs │ ├── angular-mcp-server │ │ ├── eslint.config.mjs │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ ├── angular-mcp-server.ts │ │ │ ├── prompts │ │ │ │ └── prompt-registry.ts │ │ │ ├── tools │ │ │ │ ├── ds │ │ │ │ │ ├── component │ │ │ │ │ │ ├── get-deprecated-css-classes.tool.ts │ │ │ │ │ │ ├── get-ds-component-data.tool.ts │ │ │ │ │ │ ├── list-ds-components.tool.ts │ │ │ │ │ │ └── utils │ │ │ │ │ │ ├── deprecated-css-helpers.ts │ │ │ │ │ │ ├── doc-helpers.ts │ │ │ │ │ │ ├── metadata-helpers.ts │ │ │ │ │ │ └── paths-helpers.ts │ │ │ │ │ ├── component-contract │ │ │ │ │ │ ├── builder │ │ │ │ │ │ │ ├── build-component-contract.tool.ts │ │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ │ ├── schema.ts │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ ├── spec │ │ │ │ │ │ │ │ ├── css-match.spec.ts │ │ │ │ │ │ │ │ ├── dom-slots.extractor.spec.ts │ │ │ │ │ │ │ │ ├── element-helpers.spec.ts │ │ │ │ │ │ │ │ ├── inline-styles.collector.spec.ts │ │ │ │ │ │ │ │ ├── meta.generator.spec.ts │ │ │ │ │ │ │ │ ├── public-api.extractor.spec.ts │ │ │ │ │ │ │ │ ├── styles.collector.spec.ts │ │ │ │ │ │ │ │ └── typescript-analyzer.spec.ts │ │ │ │ │ │ │ └── utils │ │ │ │ │ │ │ ├── build-contract.ts │ │ │ │ │ │ │ ├── css-match.ts │ │ │ │ │ │ │ ├── dom-slots.extractor.ts │ │ │ │ │ │ │ ├── element-helpers.ts │ │ │ │ │ │ │ ├── inline-styles.collector.ts │ │ │ │ │ │ │ ├── meta.generator.ts │ │ │ │ │ │ │ ├── public-api.extractor.ts │ │ │ │ │ │ │ ├── styles.collector.ts │ │ │ │ │ │ │ └── typescript-analyzer.ts │ │ │ │ │ │ ├── diff │ │ │ │ │ │ │ ├── diff-component-contract.tool.ts │ │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ │ └── schema.ts │ │ │ │ │ │ │ ├── spec │ │ │ │ │ │ │ │ ├── diff-utils.spec.ts │ │ │ │ │ │ │ │ └── dom-path-utils.spec.ts │ │ │ │ │ │ │ └── utils │ │ │ │ │ │ │ ├── diff-utils.ts │ │ │ │ │ │ │ └── dom-path-utils.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── list │ │ │ │ │ │ │ ├── list-component-contracts.tool.ts │ │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ │ ├── schema.ts │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ ├── spec │ │ │ │ │ │ │ │ └── contract-list-utils.spec.ts │ │ │ │ │ │ │ └── utils │ │ │ │ │ │ │ └── contract-list-utils.ts │ │ │ │ │ │ └── shared │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ ├── spec │ │ │ │ │ │ │ └── contract-file-ops.spec.ts │ │ │ │ │ │ └── utils │ │ │ │ │ │ └── contract-file-ops.ts │ │ │ │ │ ├── component-usage-graph │ │ │ │ │ │ ├── build-component-usage-graph.tool.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ ├── config.ts │ │ │ │ │ │ │ ├── schema.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ └── utils │ │ │ │ │ │ ├── angular-parser.ts │ │ │ │ │ │ ├── component-helpers.ts │ │ │ │ │ │ ├── component-usage-graph-builder.ts │ │ │ │ │ │ ├── path-resolver.ts │ │ │ │ │ │ └── unified-ast-analyzer.ts │ │ │ │ │ ├── ds.tools.ts │ │ │ │ │ ├── project │ │ │ │ │ │ ├── get-project-dependencies.tool.ts │ │ │ │ │ │ ├── report-deprecated-css.tool.ts │ │ │ │ │ │ └── utils │ │ │ │ │ │ ├── dependencies-helpers.ts │ │ │ │ │ │ └── styles-report-helpers.ts │ │ │ │ │ ├── report-violations │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ ├── schema.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ ├── report-all-violations.tool.ts │ │ │ │ │ │ └── report-violations.tool.ts │ │ │ │ │ ├── shared │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── models │ │ │ │ │ │ │ ├── input-schemas.model.ts │ │ │ │ │ │ │ └── schema-helpers.ts │ │ │ │ │ │ ├── utils │ │ │ │ │ │ │ ├── component-validation.ts │ │ │ │ │ │ │ ├── cross-platform-path.ts │ │ │ │ │ │ │ ├── handler-helpers.ts │ │ │ │ │ │ │ ├── output.utils.ts │ │ │ │ │ │ │ └── regex-helpers.ts │ │ │ │ │ │ └── violation-analysis │ │ │ │ │ │ ├── base-analyzer.ts │ │ │ │ │ │ ├── coverage-analyzer.ts │ │ │ │ │ │ ├── formatters.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── tools.ts │ │ │ │ ├── schema.ts │ │ │ │ ├── tools.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ └── validation │ │ │ ├── angular-mcp-server-options.schema.ts │ │ │ ├── ds-components-file-loader.validation.ts │ │ │ ├── ds-components-file.validation.ts │ │ │ ├── ds-components.schema.ts │ │ │ └── file-existence.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.tsbuildinfo │ │ └── vitest.config.mts │ ├── minimal-repo │ │ └── packages │ │ ├── application │ │ │ ├── angular.json │ │ │ ├── code-pushup.config.ts │ │ │ ├── src │ │ │ │ ├── app │ │ │ │ │ ├── app.component.ts │ │ │ │ │ ├── app.config.ts │ │ │ │ │ ├── app.routes.ts │ │ │ │ │ ├── components │ │ │ │ │ │ ├── refactoring-tests │ │ │ │ │ │ │ ├── bad-alert-tooltip-input.component.ts │ │ │ │ │ │ │ ├── bad-alert.component.ts │ │ │ │ │ │ │ ├── bad-button-dropdown.component.ts │ │ │ │ │ │ │ ├── bad-document.component.ts │ │ │ │ │ │ │ ├── bad-global-this.component.ts │ │ │ │ │ │ │ ├── bad-mixed-external-assets.component.css │ │ │ │ │ │ │ ├── bad-mixed-external-assets.component.html │ │ │ │ │ │ │ ├── bad-mixed-external-assets.component.ts │ │ │ │ │ │ │ ├── bad-mixed-not-standalone.component.ts │ │ │ │ │ │ │ ├── bad-mixed.component.ts │ │ │ │ │ │ │ ├── bad-mixed.module.ts │ │ │ │ │ │ │ ├── bad-modal-progress.component.ts │ │ │ │ │ │ │ ├── bad-this-window-document.component.ts │ │ │ │ │ │ │ ├── bad-window.component.ts │ │ │ │ │ │ │ ├── complex-components │ │ │ │ │ │ │ │ ├── first-case │ │ │ │ │ │ │ │ │ ├── dashboard-demo.component.html │ │ │ │ │ │ │ │ │ ├── dashboard-demo.component.scss │ │ │ │ │ │ │ │ │ ├── dashboard-demo.component.ts │ │ │ │ │ │ │ │ │ ├── dashboard-header.component.html │ │ │ │ │ │ │ │ │ ├── dashboard-header.component.scss │ │ │ │ │ │ │ │ │ └── dashboard-header.component.ts │ │ │ │ │ │ │ │ ├── second-case │ │ │ │ │ │ │ │ │ ├── complex-badge-widget.component.scss │ │ │ │ │ │ │ │ │ ├── complex-badge-widget.component.ts │ │ │ │ │ │ │ │ │ └── complex-widget-demo.component.ts │ │ │ │ │ │ │ │ └── third-case │ │ │ │ │ │ │ │ ├── product-card.component.scss │ │ │ │ │ │ │ │ ├── product-card.component.ts │ │ │ │ │ │ │ │ └── product-showcase.component.ts │ │ │ │ │ │ │ ├── group-1 │ │ │ │ │ │ │ │ ├── bad-mixed-1.component.ts │ │ │ │ │ │ │ │ ├── bad-mixed-1.module.ts │ │ │ │ │ │ │ │ ├── bad-mixed-external-assets-1.component.css │ │ │ │ │ │ │ │ ├── bad-mixed-external-assets-1.component.html │ │ │ │ │ │ │ │ ├── bad-mixed-external-assets-1.component.ts │ │ │ │ │ │ │ │ └── bad-mixed-not-standalone-1.component.ts │ │ │ │ │ │ │ ├── group-2 │ │ │ │ │ │ │ │ ├── bad-mixed-2.component.ts │ │ │ │ │ │ │ │ ├── bad-mixed-2.module.ts │ │ │ │ │ │ │ │ ├── bad-mixed-external-assets-2.component.css │ │ │ │ │ │ │ │ ├── bad-mixed-external-assets-2.component.html │ │ │ │ │ │ │ │ ├── bad-mixed-external-assets-2.component.ts │ │ │ │ │ │ │ │ └── bad-mixed-not-standalone-2.component.ts │ │ │ │ │ │ │ ├── group-3 │ │ │ │ │ │ │ │ ├── bad-mixed-3.component.spec.ts │ │ │ │ │ │ │ │ ├── bad-mixed-3.component.ts │ │ │ │ │ │ │ │ ├── bad-mixed-3.module.ts │ │ │ │ │ │ │ │ ├── bad-mixed-external-assets-3.component.css │ │ │ │ │ │ │ │ ├── bad-mixed-external-assets-3.component.html │ │ │ │ │ │ │ │ ├── bad-mixed-external-assets-3.component.ts │ │ │ │ │ │ │ │ ├── bad-mixed-not-standalone-3.component.ts │ │ │ │ │ │ │ │ └── lazy-loader-3.component.ts │ │ │ │ │ │ │ └── group-4 │ │ │ │ │ │ │ ├── multi-violation-test.component.html │ │ │ │ │ │ │ ├── multi-violation-test.component.scss │ │ │ │ │ │ │ └── multi-violation-test.component.ts │ │ │ │ │ │ └── validation-tests │ │ │ │ │ │ ├── circular-dependency.component.ts │ │ │ │ │ │ ├── external-files-missing.component.ts │ │ │ │ │ │ ├── invalid-lifecycle.component.ts │ │ │ │ │ │ ├── invalid-pipe-usage.component.ts │ │ │ │ │ │ ├── invalid-template-syntax.component.ts │ │ │ │ │ │ ├── missing-imports.component.ts │ │ │ │ │ │ ├── missing-method.component.ts │ │ │ │ │ │ ├── README.md │ │ │ │ │ │ ├── standalone-module-conflict.component.ts │ │ │ │ │ │ ├── standalone-module-conflict.module.ts │ │ │ │ │ │ ├── template-reference-error.component.ts │ │ │ │ │ │ ├── type-mismatch.component.ts │ │ │ │ │ │ ├── valid.component.ts │ │ │ │ │ │ ├── wrong-decorator-usage.component.ts │ │ │ │ │ │ └── wrong-property-binding.component.ts │ │ │ │ │ └── styles │ │ │ │ │ ├── bad-global-styles.scss │ │ │ │ │ ├── base │ │ │ │ │ │ ├── _reset.scss │ │ │ │ │ │ └── base.scss │ │ │ │ │ ├── components │ │ │ │ │ │ └── components.scss │ │ │ │ │ ├── extended-deprecated-styles.scss │ │ │ │ │ ├── layout │ │ │ │ │ │ └── layout.scss │ │ │ │ │ ├── new-styles-1.scss │ │ │ │ │ ├── new-styles-10.scss │ │ │ │ │ ├── new-styles-2.scss │ │ │ │ │ ├── new-styles-3.scss │ │ │ │ │ ├── new-styles-4.scss │ │ │ │ │ ├── new-styles-5.scss │ │ │ │ │ ├── new-styles-6.scss │ │ │ │ │ ├── new-styles-7.scss │ │ │ │ │ ├── new-styles-8.scss │ │ │ │ │ ├── new-styles-9.scss │ │ │ │ │ ├── themes │ │ │ │ │ │ └── themes.scss │ │ │ │ │ └── utilities │ │ │ │ │ └── utilities.scss │ │ │ │ ├── index.html │ │ │ │ ├── main.ts │ │ │ │ └── styles.css │ │ │ ├── tsconfig.app.json │ │ │ ├── tsconfig.json │ │ │ └── tsconfig.spec.json │ │ └── design-system │ │ ├── component-options.mjs │ │ ├── storybook │ │ │ └── card │ │ │ └── card-tabs │ │ │ └── overview.mdx │ │ ├── storybook-host-app │ │ │ └── src │ │ │ └── components │ │ │ ├── badge │ │ │ │ ├── badge-tabs │ │ │ │ │ ├── api.mdx │ │ │ │ │ ├── examples.mdx │ │ │ │ │ └── overview.mdx │ │ │ │ ├── badge.component.mdx │ │ │ │ └── badge.component.stories.ts │ │ │ ├── modal │ │ │ │ ├── demo-cdk-dialog-cmp.component.ts │ │ │ │ ├── demo-modal-cmp.component.ts │ │ │ │ ├── modal-tabs │ │ │ │ │ ├── api.mdx │ │ │ │ │ ├── examples.mdx │ │ │ │ │ └── overview.mdx │ │ │ │ ├── modal.component.mdx │ │ │ │ └── modal.component.stories.ts │ │ │ └── segmented-control │ │ │ ├── segmented-control-tabs │ │ │ │ ├── api.mdx │ │ │ │ ├── examples.mdx │ │ │ │ └── overview.mdx │ │ │ ├── segmented-control.component.mdx │ │ │ └── segmented-control.component.stories.ts │ │ └── ui │ │ ├── badge │ │ │ ├── package.json │ │ │ ├── project.json │ │ │ └── src │ │ │ └── badge.component.ts │ │ ├── modal │ │ │ ├── package.json │ │ │ ├── project.json │ │ │ └── src │ │ │ ├── modal-content.component.ts │ │ │ ├── modal-header │ │ │ │ └── modal-header.component.ts │ │ │ ├── modal-header-drag │ │ │ │ └── modal-header-drag.component.ts │ │ │ └── modal.component.ts │ │ ├── rx-host-listener │ │ │ ├── package.json │ │ │ ├── project.json │ │ │ └── src │ │ │ └── rx-host-listener.ts │ │ └── segmented-control │ │ ├── package.json │ │ ├── project.json │ │ └── src │ │ ├── segmented-control.component.html │ │ ├── segmented-control.component.ts │ │ ├── segmented-control.token.ts │ │ └── segmented-option.component.ts │ └── shared │ ├── angular-ast-utils │ │ ├── .spec.swcrc │ │ ├── ai │ │ │ ├── API.md │ │ │ ├── EXAMPLES.md │ │ │ └── FUNCTIONS.md │ │ ├── docs │ │ │ └── angular-component-tree.md │ │ ├── eslint.config.mjs │ │ ├── jest.config.ts │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ ├── constants.ts │ │ │ ├── decorator-config.visitor.inline-styles.spec.ts │ │ │ ├── decorator-config.visitor.spec.ts │ │ │ ├── decorator-config.visitor.ts │ │ │ ├── parse-component.ts │ │ │ ├── schema.ts │ │ │ ├── styles │ │ │ │ └── utils.ts │ │ │ ├── template │ │ │ │ ├── noop-tmpl-visitor.ts │ │ │ │ ├── template.walk.ts │ │ │ │ ├── utils.spec.ts │ │ │ │ ├── utils.ts │ │ │ │ └── utils.unit.test.ts │ │ │ ├── ts.walk.ts │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── vitest.config.mts │ ├── DEPENDENCIES.md │ ├── ds-component-coverage │ │ ├── .spec.swcrc │ │ ├── ai │ │ │ ├── API.md │ │ │ ├── EXAMPLES.md │ │ │ └── FUNCTIONS.md │ │ ├── docs │ │ │ ├── examples │ │ │ │ ├── report.json │ │ │ │ └── report.md │ │ │ ├── images │ │ │ │ └── report-overview.png │ │ │ └── README.md │ │ ├── jest.config.ts │ │ ├── mocks │ │ │ └── fixtures │ │ │ └── e2e │ │ │ ├── asset-location │ │ │ │ ├── code-pushup.config.ts │ │ │ │ ├── inl-styl-inl-tmpl │ │ │ │ │ └── inl-styl-inl-tmpl.component.ts │ │ │ │ ├── inl-styl-url-tmpl │ │ │ │ │ ├── inl-styl-url-tmpl.component.html │ │ │ │ │ └── inl-styl-url-tmpl.component.ts │ │ │ │ ├── multi-url-styl-inl-tmpl │ │ │ │ │ ├── multi-url-styl-inl-tmpl-1.component.css │ │ │ │ │ ├── multi-url-styl-inl-tmpl-2.component.css │ │ │ │ │ └── multi-url-styl-inl-tmpl.component.ts │ │ │ │ ├── url-styl-inl-tmpl │ │ │ │ │ ├── url-styl-inl-tmpl.component.css │ │ │ │ │ └── url-styl-inl-tmpl.component.ts │ │ │ │ ├── url-styl-single-inl-tmpl │ │ │ │ │ ├── url-styl-inl-tmpl.component.ts │ │ │ │ │ └── url-styl-single-inl-tmpl.component.css │ │ │ │ └── url-styl-url-tmpl │ │ │ │ ├── inl-styl-url-tmpl.component.css │ │ │ │ ├── inl-styl-url-tmpl.component.html │ │ │ │ └── inl-styl-url-tmpl.component.ts │ │ │ ├── demo │ │ │ │ ├── code-pushup.config.ts │ │ │ │ ├── prompt.md │ │ │ │ └── src │ │ │ │ ├── bad-button-dropdown.component.ts │ │ │ │ ├── bad-modal-progress.component.ts │ │ │ │ ├── mixed-external-assets.component.css │ │ │ │ ├── mixed-external-assets.component.html │ │ │ │ ├── mixed-external-assets.component.ts │ │ │ │ └── sub-folder-1 │ │ │ │ ├── bad-alert.component.ts │ │ │ │ ├── button.component.ts │ │ │ │ └── sub-folder-2 │ │ │ │ ├── bad-alert-tooltip-input.component.ts │ │ │ │ └── bad-mixed.component.ts │ │ │ ├── line-number │ │ │ │ ├── code-pushup.config.ts │ │ │ │ ├── inl-styl-single.component.ts │ │ │ │ ├── inl-styl-span.component.ts │ │ │ │ ├── inl-tmpl-single.component.ts │ │ │ │ ├── inl-tmpl-span.component.ts │ │ │ │ ├── url-style │ │ │ │ │ ├── url-styl-single.component.css │ │ │ │ │ ├── url-styl-single.component.ts │ │ │ │ │ ├── url-styl-span.component.css │ │ │ │ │ └── url-styl-span.component.ts │ │ │ │ └── url-tmpl │ │ │ │ ├── url-tmpl-single.component.html │ │ │ │ ├── url-tmpl-single.component.ts │ │ │ │ ├── url-tmpl-span.component.html │ │ │ │ └── url-tmpl-span.component.ts │ │ │ ├── style-format │ │ │ │ ├── code-pushup.config.ts │ │ │ │ ├── inl-css.component.ts │ │ │ │ ├── inl-scss.component.ts │ │ │ │ ├── styles.css │ │ │ │ ├── styles.scss │ │ │ │ ├── url-css.component.ts │ │ │ │ └── url-scss.component.ts │ │ │ └── template-syntax │ │ │ ├── class-attribute.component.ts │ │ │ ├── class-binding.component.ts │ │ │ ├── code-pushup.config.ts │ │ │ └── ng-class-binding.component.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── core.config.ts │ │ │ ├── index.ts │ │ │ └── lib │ │ │ ├── constants.ts │ │ │ ├── ds-component-coverage.plugin.ts │ │ │ ├── runner │ │ │ │ ├── audits │ │ │ │ │ └── ds-coverage │ │ │ │ │ ├── class-definition.utils.ts │ │ │ │ │ ├── class-definition.visitor.ts │ │ │ │ │ ├── class-definition.visitor.unit.test.ts │ │ │ │ │ ├── class-usage.utils.ts │ │ │ │ │ ├── class-usage.visitor.spec.ts │ │ │ │ │ ├── class-usage.visitor.ts │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── ds-coverage.audit.ts │ │ │ │ │ ├── schema.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── create-runner.ts │ │ │ │ └── schema.ts │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── vitest.config.mts │ ├── LLMS.md │ ├── models │ │ ├── ai │ │ │ ├── API.md │ │ │ ├── EXAMPLES.md │ │ │ └── FUNCTIONS.md │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ ├── cli.ts │ │ │ ├── diagnostics.ts │ │ │ └── mcp.ts │ │ ├── tsconfig.json │ │ └── tsconfig.lib.json │ ├── styles-ast-utils │ │ ├── .spec.swcrc │ │ ├── ai │ │ │ ├── API.md │ │ │ ├── EXAMPLES.md │ │ │ └── FUNCTIONS.md │ │ ├── jest.config.ts │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ ├── postcss-safe-parser.d.ts │ │ │ ├── styles-ast-utils.spec.ts │ │ │ ├── styles-ast-utils.ts │ │ │ ├── stylesheet.parse.ts │ │ │ ├── stylesheet.parse.unit.test.ts │ │ │ ├── stylesheet.visitor.ts │ │ │ ├── stylesheet.walk.ts │ │ │ ├── types.ts │ │ │ ├── utils.ts │ │ │ └── utils.unit.test.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── vitest.config.mts │ ├── typescript-ast-utils │ │ ├── .spec.swcrc │ │ ├── ai │ │ │ ├── API.md │ │ │ ├── EXAMPLES.md │ │ │ └── FUNCTIONS.md │ │ ├── jest.config.ts │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ ├── constants.ts │ │ │ └── utils.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── vitest.config.mts │ └── utils │ ├── .spec.swcrc │ ├── ai │ │ ├── API.md │ │ ├── EXAMPLES.md │ │ └── FUNCTIONS.md │ ├── package.json │ ├── README.md │ ├── src │ │ ├── index.ts │ │ └── lib │ │ ├── execute-process.ts │ │ ├── execute-process.unit.test.ts │ │ ├── file │ │ │ ├── default-export-loader.spec.ts │ │ │ ├── default-export-loader.ts │ │ │ ├── file.resolver.ts │ │ │ └── find-in-file.ts │ │ ├── format-command-log.integration.test.ts │ │ ├── format-command-log.ts │ │ ├── logging.ts │ │ └── utils.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ ├── vite.config.ts │ └── vitest.config.mts ├── README.md ├── testing │ ├── setup │ │ ├── eslint.config.mjs │ │ ├── eslint.next.config.mjs │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.d.ts │ │ │ ├── index.mjs │ │ │ └── memfs.constants.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ ├── vitest.config.mts │ │ └── vitest.integration.config.mts │ ├── utils │ │ ├── eslint.config.mjs │ │ ├── eslint.next.config.mjs │ │ ├── package.json │ │ ├── README.md │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ ├── constants.ts │ │ │ ├── e2e-setup.ts │ │ │ ├── execute-process-helper.mock.ts │ │ │ ├── execute-process.mock.mjs │ │ │ ├── os-agnostic-paths.ts │ │ │ ├── os-agnostic-paths.unit.test.ts │ │ │ ├── source-file-from.code.ts │ │ │ └── string.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ ├── vite.config.ts │ │ ├── vitest.config.mts │ │ └── vitest.integration.config.mts │ └── vitest-setup │ ├── eslint.config.mjs │ ├── eslint.next.config.mjs │ ├── package.json │ ├── README.md │ ├── src │ │ ├── index.ts │ │ └── lib │ │ ├── configuration.ts │ │ └── fs-memfs.setup-file.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ ├── vite.config.ts │ ├── vitest.config.mts │ └── vitest.integration.config.mts ├── tools │ ├── nx-advanced-profile.bin.js │ ├── nx-advanced-profile.js │ ├── nx-advanced-profile.postinstall.js │ └── perf_hooks.patch.js ├── tsconfig.base.json ├── tsconfig.json └── vitest.workspace.ts ``` # Files -------------------------------------------------------------------------------- /packages/shared/ds-component-coverage/src/lib/runner/audits/ds-coverage/class-definition.utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Issue } from '@code-pushup/models'; 2 | import { Asset } from '@push-based/angular-ast-utils'; 3 | import { type Root } from 'postcss'; 4 | import { ComponentReplacement } from './schema.js'; 5 | import { visitEachStyleNode } from '@push-based/styles-ast-utils'; 6 | import { createClassDefinitionVisitor } from './class-definition.visitor.js'; 7 | 8 | export async function getClassDefinitionIssues( 9 | componentReplacement: ComponentReplacement, 10 | style: Asset<Root>, 11 | ): Promise<Issue[]> { 12 | const stylesVisitor = createClassDefinitionVisitor( 13 | componentReplacement, 14 | style.startLine, 15 | ); 16 | const ast = (await style.parse()).root as unknown as Root; 17 | visitEachStyleNode(ast.nodes, stylesVisitor); 18 | 19 | return stylesVisitor.getIssues(); 20 | } 21 | ``` -------------------------------------------------------------------------------- /packages/shared/angular-ast-utils/src/lib/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Root } from 'postcss'; 2 | import type { ParsedTemplate } from '@angular/compiler' with { 'resolution-mode': 'import' }; 3 | import { z } from 'zod'; 4 | import { AngularUnitSchema } from './schema.js'; 5 | 6 | export type Asset<T> = SourceLink & { 7 | parse: () => Promise<T>; 8 | }; 9 | 10 | export type SourceLink = { filePath: string; startLine: number }; 11 | 12 | export type ParsedComponent = { 13 | className: string; 14 | fileName: string; 15 | startLine: number; 16 | } & { 17 | templateUrl: Asset<ParsedTemplate>; 18 | template: Asset<ParsedTemplate>; 19 | styles: Asset<Root>[]; 20 | styleUrls: Asset<Root>[]; 21 | styleUrl: Asset<Root>; 22 | } & { 23 | [key: string]: string; // @TODO implement all of the component props 24 | }; 25 | export type AngularUnit = z.infer<typeof AngularUnitSchema>; 26 | ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compileOnSave": false, 4 | "files": [], 5 | "references": [ 6 | { 7 | "path": "./packages/angular-mcp-server" 8 | }, 9 | { 10 | "path": "./packages/angular-mcp" 11 | }, 12 | { 13 | "path": "./packages/shared/typescript-ast-utils" 14 | }, 15 | { 16 | "path": "./packages/shared/models" 17 | }, 18 | { 19 | "path": "./packages/shared/angular-ast-utils" 20 | }, 21 | { 22 | "path": "./packages/shared/styles-ast-utils" 23 | }, 24 | { 25 | "path": "./packages/shared/utils" 26 | }, 27 | { 28 | "path": "./packages/shared/ds-component-coverage" 29 | }, 30 | { 31 | "path": "./testing/vitest-setup" 32 | }, 33 | { 34 | "path": "./testing/setup" 35 | }, 36 | { 37 | "path": "./testing/utils" 38 | } 39 | ] 40 | } 41 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ToolSchemaOptions } from '@push-based/models'; 2 | 3 | export const baseToolsSchema: ToolSchemaOptions = { 4 | name: 'base_tools', 5 | description: 'Base tools for Angular MCP server', 6 | inputSchema: { 7 | type: 'object', 8 | properties: { 9 | cwd: { 10 | type: 'string', 11 | description: 'The current working directory e.g. repository root', 12 | }, 13 | workspaceRoot: { 14 | type: 'string', 15 | description: 'The absolute path to the workspace root directory', 16 | }, 17 | verbose: { 18 | type: 'boolean', 19 | description: 'Enable verbose logging', 20 | }, 21 | }, 22 | required: ['cwd'], 23 | }, 24 | }; 25 | 26 | export type CliArguments = { 27 | cwd: string; 28 | workspaceRoot: string; 29 | verbose?: string; 30 | [key: string]: unknown; 31 | }; 32 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/design-system/storybook-host-app/src/components/modal/modal-tabs/examples.mdx: -------------------------------------------------------------------------------- ```markdown 1 | import { Canvas } from '@storybook/blocks'; 2 | 3 | import * as ModalStories from '../modal.component.stories'; 4 | 5 | ## DsModal 6 | 7 | <Canvas of={ModalStories.Default} /> 8 | 9 | ## DsModalHeader and DsModalContent 10 | 11 | <Canvas of={ModalStories.WithTitleAndClose} /> 12 | 13 | ## DsModalHeaderDrag 14 | 15 | <Canvas of={ModalStories.WithDragger} /> 16 | 17 | ## Usage with CdkDialog 18 | 19 | <Canvas of={ModalStories.WithCdkDialog} /> 20 | 21 | ## Usage with MatDialog 22 | 23 | <Canvas of={ModalStories.WithMatDialog} /> 24 | 25 | ## Other examples 26 | 27 | <Canvas of={ModalStories.BolderSubtitleThanTitle}></Canvas> 28 | 29 | <Canvas of={ModalStories.CloseTitleLabelAction} /> 30 | 31 | <Canvas of={ModalStories.ActionsAndCenteredTitle} /> 32 | 33 | <Canvas of={ModalStories.BackBtnTitleLabelAction} /> 34 | 35 | ## All variants 36 | 37 | <Canvas of={ModalStories.ModalHeaderTypes} /> 38 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/meta.generator.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { basename, extname } from 'node:path'; 2 | import type { Meta, TemplateType } from '../../shared/models/types.js'; 3 | import { ParsedComponent } from '@push-based/angular-ast-utils'; 4 | 5 | /** 6 | * Generate contract metadata 7 | */ 8 | export function generateMeta( 9 | templatePath: string, 10 | parsedComponent: ParsedComponent, 11 | isInlineTemplate = false, 12 | ): Meta { 13 | const componentName = basename(templatePath, extname(templatePath)); 14 | const templateType: TemplateType = isInlineTemplate ? 'inline' : 'external'; 15 | 16 | return { 17 | name: parsedComponent.className || componentName, 18 | selector: parsedComponent.selector || `.${componentName}`, 19 | sourceFile: templatePath, 20 | templateType, 21 | generatedAt: new Date().toISOString(), 22 | hash: '', // Will be filled later 23 | }; 24 | } 25 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/styles/base/_reset.scss: -------------------------------------------------------------------------------- ```scss 1 | /* Modern CSS Reset */ 2 | *, 3 | *::before, 4 | *::after { 5 | box-sizing: border-box; 6 | } 7 | 8 | * { 9 | margin: 0; 10 | } 11 | 12 | body { 13 | line-height: 1.5; 14 | -webkit-font-smoothing: antialiased; 15 | } 16 | 17 | img, 18 | picture, 19 | video, 20 | canvas, 21 | svg { 22 | display: block; 23 | max-width: 100%; 24 | } 25 | 26 | input, 27 | button, 28 | textarea, 29 | select { 30 | font: inherit; 31 | } 32 | 33 | p, 34 | h1, 35 | h2, 36 | h3, 37 | h4, 38 | h5, 39 | h6 { 40 | overflow-wrap: break-word; 41 | } 42 | 43 | #root, 44 | #__next { 45 | isolation: isolate; 46 | } 47 | 48 | /* Remove default button styles */ 49 | button { 50 | background: none; 51 | border: none; 52 | padding: 0; 53 | cursor: pointer; 54 | } 55 | 56 | /* Remove default list styles */ 57 | ul, 58 | ol { 59 | list-style: none; 60 | padding: 0; 61 | } 62 | 63 | /* Remove default link styles */ 64 | a { 65 | text-decoration: none; 66 | color: inherit; 67 | } 68 | 69 | /* Focus styles */ 70 | :focus-visible { 71 | outline: 2px solid #007bff; 72 | outline-offset: 2px; 73 | } 74 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/design-system/storybook-host-app/src/components/badge/badge-tabs/examples.mdx: -------------------------------------------------------------------------------- ```markdown 1 | import { Canvas } from '@storybook/blocks'; 2 | 3 | import * as BadgeStories from '../badge.component.stories'; 4 | 5 | ## Default 6 | 7 | <Canvas of={BadgeStories.Default} /> 8 | 9 | ## With Icon 10 | 11 | ### Only for xsmall and medium badges 12 | 13 | <Canvas of={BadgeStories.WithIcon} /> 14 | 15 | ## With Icon Only 16 | 17 | ### Only for xsmall and medium badges 18 | 19 | <Canvas of={BadgeStories.WithIconOnly} /> 20 | 21 | ## With Notification Bubble 22 | 23 | ### Only for medium badges 24 | 25 | <Canvas of={BadgeStories.WithNotificationBubble} /> 26 | 27 | ## With Success 28 | 29 | ### Only for xsmall and medium badges 30 | 31 | <Canvas of={BadgeStories.WithSuccess} /> 32 | 33 | ## With Icon and Success 34 | 35 | ### Only for xsmall and medium badges 36 | 37 | <Canvas of={BadgeStories.WithIconAndSuccess} /> 38 | 39 | ## All Variants 40 | 41 | <Canvas of={BadgeStories.LargeExamples} withToolbar={false} withSource="none" /> 42 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/validation-tests/missing-imports.component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-missing-imports', 5 | standalone: true, 6 | template: ` 7 | <div> 8 | <h2>Missing Imports Test</h2> 9 | <input [(ngModel)]="inputValue" placeholder="Two-way binding without FormsModule" /> 10 | <div *ngIf="showContent">Conditional content without CommonModule</div> 11 | <ul> 12 | <li *ngFor="let item of items">{{ item | uppercase }}</li> 13 | </ul> 14 | <form #myForm="ngForm"> 15 | <input name="test" ngModel required /> 16 | <button type="submit" [disabled]="myForm.invalid">Submit</button> 17 | </form> 18 | </div> 19 | `, 20 | styles: [` 21 | div { padding: 10px; } 22 | `] 23 | }) 24 | export class MissingImportsComponent { 25 | inputValue = ''; 26 | showContent = true; 27 | items = ['apple', 'banana', 'cherry']; 28 | } ``` -------------------------------------------------------------------------------- /packages/shared/utils/vite.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { defineConfig } from 'vite'; 2 | import { resolve } from 'path'; 3 | // eslint-disable-next-line @nx/enforce-module-boundaries 4 | import { createSharedVitestConfig } from '../../../testing/vitest-setup/src/lib/configuration'; 5 | 6 | const sharedConfig = createSharedVitestConfig({ 7 | projectRoot: __dirname, 8 | workspaceRoot: '../../../', 9 | environment: 'jsdom', 10 | }); 11 | 12 | export default defineConfig(() => ({ 13 | root: __dirname, 14 | cacheDir: '../../../node_modules/.vite/packages/shared/utils', 15 | plugins: [], 16 | resolve: { 17 | alias: { 18 | '@push-based/testing-utils': resolve( 19 | __dirname, 20 | '../../../testing/utils/src/index.ts', 21 | ), 22 | }, 23 | }, 24 | // Uncomment this if you are using workers. 25 | // worker: { 26 | // plugins: [ nxViteTsPaths() ], 27 | // }, 28 | test: sharedConfig.test, 29 | })); 30 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/tsconfig.lib.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "baseUrl": ".", 7 | "rootDir": "src", 8 | "outDir": "dist", 9 | "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", 10 | "emitDeclarationOnly": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true, 13 | "types": ["node"] 14 | }, 15 | "include": ["src/**/*.ts"], 16 | "references": [ 17 | { 18 | "path": "../shared/ds-component-coverage/tsconfig.lib.json" 19 | }, 20 | { 21 | "path": "../shared/styles-ast-utils/tsconfig.lib.json" 22 | }, 23 | { 24 | "path": "../shared/angular-ast-utils/tsconfig.lib.json" 25 | }, 26 | { 27 | "path": "../shared/utils/tsconfig.lib.json" 28 | }, 29 | { 30 | "path": "../shared/models/tsconfig.lib.json" 31 | } 32 | ] 33 | } 34 | ``` -------------------------------------------------------------------------------- /packages/shared/utils/src/lib/format-command-log.ts: -------------------------------------------------------------------------------- ```typescript 1 | import ansis from 'ansis'; 2 | import path from 'node:path'; 3 | 4 | /** 5 | * Formats a command string with optional cwd prefix and ANSI colors. 6 | * 7 | * @param {string} command - The command to execute. 8 | * @param {string[]} args - Array of command arguments. 9 | * @param {string} [cwd] - Optional current working directory for the command. 10 | * @returns {string} - ANSI-colored formatted command string. 11 | */ 12 | export function formatCommandLog( 13 | command: string, 14 | args: string[] = [], 15 | cwd: string = process.cwd(), 16 | ): string { 17 | const relativeDir = path.relative(process.cwd(), cwd); 18 | 19 | return [ 20 | ...(relativeDir && relativeDir !== '.' 21 | ? [ansis.italic(ansis.gray(relativeDir))] 22 | : []), 23 | ansis.yellow('$'), 24 | ansis.gray(command), 25 | ansis.gray(args.map((arg) => arg).join(' ')), 26 | ].join(' '); 27 | } 28 | ``` -------------------------------------------------------------------------------- /packages/shared/models/ai/FUNCTIONS.md: -------------------------------------------------------------------------------- ```markdown 1 | # Public API — Quick Reference 2 | 3 | | Symbol | Kind | Summary | 4 | | -------------------------- | --------- | -------------------------------------------------------- | 5 | | `ArgumentValue` | type | CLI argument value types (number, string, boolean, array) | 6 | | `CliArgsObject` | type | CLI arguments object with typed values | 7 | | `DiagnosticsAware` | interface | Interface for objects that can report issues | 8 | | `ToolHandlerContentResult` | type | MCP tool handler content result | 9 | | `ToolSchemaOptions` | type | MCP tool schema configuration options | 10 | | `ToolsConfig` | type | MCP tools configuration with schema and handler | 11 | ``` -------------------------------------------------------------------------------- /packages/shared/utils/tsconfig.lib.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "baseUrl": ".", 7 | "rootDir": "src", 8 | "outDir": "dist", 9 | "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", 10 | "emitDeclarationOnly": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "types": ["node"] 13 | }, 14 | "include": ["src/**/*.ts"], 15 | "references": [ 16 | { 17 | "path": "../../../testing/vitest-setup/tsconfig.lib.json" 18 | }, 19 | { 20 | "path": "../models/tsconfig.lib.json" 21 | } 22 | ], 23 | "exclude": [ 24 | "jest.config.ts", 25 | "src/**/*.spec.ts", 26 | "src/**/*.test.ts", 27 | "vite.config.ts", 28 | "vite.config.mts", 29 | "vitest.config.ts", 30 | "vitest.config.mts", 31 | "src/**/*.test.tsx", 32 | "src/**/*.spec.tsx", 33 | "src/**/*.test.js", 34 | "src/**/*.spec.js", 35 | "src/**/*.test.jsx", 36 | "src/**/*.spec.jsx" 37 | ] 38 | } 39 | ``` -------------------------------------------------------------------------------- /packages/shared/styles-ast-utils/src/lib/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { AtRule, Comment, Container, Declaration, Rule } from 'postcss'; 2 | 3 | export type NodeType<K extends keyof CssAstVisitor> = K extends 'visitRoot' 4 | ? Container 5 | : K extends 'visitAtRule' 6 | ? AtRule 7 | : K extends 'visitRule' 8 | ? Rule 9 | : K extends 'visitDecl' 10 | ? Declaration 11 | : K extends 'visitComment' 12 | ? Comment 13 | : never; 14 | 15 | export interface CssAstVisitor<T = void> { 16 | // Called once for the root node 17 | visitRoot?: (root: Container) => T; 18 | 19 | // Called for @rule nodes: @media, @charset, etc. 20 | visitAtRule?: (atRule: AtRule) => T; 21 | 22 | // Called for standard CSS rule nodes: .btn, .box, etc. 23 | visitRule?: (rule: Rule) => T; 24 | 25 | // Called for property declarations: color: red, width: 100px, etc. 26 | visitDecl?: (decl: Declaration) => T; 27 | 28 | // Called for comment nodes: /* some comment */ 29 | visitComment?: (comment: Comment) => T; 30 | } 31 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/models/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Builder Module Types 3 | * Builder-specific result types - core contract types are in shared models/types.ts 4 | */ 5 | 6 | import type { ComponentContract } from '../../shared/models/types.js'; 7 | 8 | export interface ContractResult { 9 | contract: ComponentContract; 10 | hash: string; 11 | contractFilePath: string; 12 | } 13 | 14 | export interface DecoratorInputMeta { 15 | name: string; 16 | type?: string; 17 | required?: boolean; 18 | alias?: string; 19 | } 20 | 21 | export interface DecoratorOutputMeta { 22 | name: string; 23 | type?: string; 24 | alias?: string; 25 | } 26 | 27 | export interface SignalInputMeta { 28 | name: string; 29 | type?: string; 30 | defaultValue?: string; 31 | required?: boolean; 32 | transform?: string; 33 | } 34 | 35 | export interface SignalOutputMeta { 36 | name: string; 37 | type?: string; 38 | } 39 | 40 | export interface ExtractedInputsOutputs { 41 | inputs: Record<string, DecoratorInputMeta | SignalInputMeta>; 42 | outputs: Record<string, DecoratorOutputMeta | SignalOutputMeta>; 43 | } 44 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "compileOnSave": false, 5 | "compilerOptions": { 6 | "outDir": "./dist/out-tsc", 7 | "strict": true, 8 | "noImplicitOverride": true, 9 | "noPropertyAccessFromIndexSignature": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "skipLibCheck": true, 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "bundler", 17 | "importHelpers": true, 18 | "target": "ES2022", 19 | "module": "ES2022" 20 | }, 21 | "angularCompilerOptions": { 22 | "enableI18nLegacyMessageIdFormat": false, 23 | "strictInjectionParameters": true, 24 | "strictInputAccessModifiers": true, 25 | "strictTemplates": true 26 | } 27 | } 28 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/bad-mixed-external-assets.component.html: -------------------------------------------------------------------------------- ```html 1 | <!-- ❌ Bad: Using legacy button styles --> 2 | <button class="btn btn-primary">Legacy Button</button> 3 | 4 | <!-- ❌ Bad: Using custom modal structure --> 5 | <div class="modal"> 6 | <div class="modal-content"> 7 | <h2>Legacy Modal</h2> 8 | <p>This is a bad example of a modal.</p> 9 | </div> 10 | </div> 11 | 12 | <!-- ❌ Bad: Using legacy progress bar --> 13 | <div class="progress-bar"> 14 | <div class="progress" style="width: 50%"></div> 15 | </div> 16 | 17 | <!-- ❌ Bad: Hardcoded alert styles --> 18 | <div class="alert alert-danger">This is a legacy alert!</div> 19 | 20 | <!-- ❌ Bad: Using a custom dropdown instead of a component --> 21 | <select class="dropdown"> 22 | <option>Option 1</option> 23 | <option>Option 2</option> 24 | </select> 25 | 26 | <!-- ❌ Bad: Using a tooltip div instead of a component --> 27 | <div class="tooltip">Hover over me for info</div> 28 | 29 | <!-- ❌ Bad: Manually created breadcrumb navigation --> 30 | <nav class="breadcrumb"> 31 | <span>Home</span> / <span>Products</span> / <span>Details</span> 32 | </nav> 33 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/group-1/bad-mixed-external-assets-1.component.html: -------------------------------------------------------------------------------- ```html 1 | <!-- ❌ Bad: Using legacy button styles --> 2 | <button class="btn btn-primary">Legacy Button</button> 3 | 4 | <!-- ❌ Bad: Using custom modal structure --> 5 | <div class="modal"> 6 | <div class="modal-content"> 7 | <h2>Legacy Modal</h2> 8 | <p>This is a bad example of a modal.</p> 9 | </div> 10 | </div> 11 | 12 | <!-- ❌ Bad: Using legacy progress bar --> 13 | <div class="progress-bar"> 14 | <div class="progress" style="width: 50%"></div> 15 | </div> 16 | 17 | <!-- ❌ Bad: Hardcoded alert styles --> 18 | <div class="alert alert-danger">This is a legacy alert!</div> 19 | 20 | <!-- ❌ Bad: Using a custom dropdown instead of a component --> 21 | <select class="dropdown"> 22 | <option>Option 1</option> 23 | <option>Option 2</option> 24 | </select> 25 | 26 | <!-- ❌ Bad: Using a tooltip div instead of a component --> 27 | <div class="tooltip">Hover over me for info</div> 28 | 29 | <!-- ❌ Bad: Manually created breadcrumb navigation --> 30 | <nav class="breadcrumb"> 31 | <span>Home</span> / <span>Products</span> / <span>Details</span> 32 | </nav> 33 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/group-2/bad-mixed-external-assets-2.component.html: -------------------------------------------------------------------------------- ```html 1 | <!-- ❌ Bad: Using legacy button styles --> 2 | <button class="btn btn-primary">Legacy Button</button> 3 | 4 | <!-- ❌ Bad: Using custom modal structure --> 5 | <div class="modal"> 6 | <div class="modal-content"> 7 | <h2>Legacy Modal</h2> 8 | <p>This is a bad example of a modal.</p> 9 | </div> 10 | </div> 11 | 12 | <!-- ❌ Bad: Using legacy progress bar --> 13 | <div class="progress-bar"> 14 | <div class="progress" style="width: 50%"></div> 15 | </div> 16 | 17 | <!-- ❌ Bad: Hardcoded alert styles --> 18 | <div class="alert alert-danger">This is a legacy alert!</div> 19 | 20 | <!-- ❌ Bad: Using a custom dropdown instead of a component --> 21 | <select class="dropdown"> 22 | <option>Option 1</option> 23 | <option>Option 2</option> 24 | </select> 25 | 26 | <!-- ❌ Bad: Using a tooltip div instead of a component --> 27 | <div class="tooltip">Hover over me for info</div> 28 | 29 | <!-- ❌ Bad: Manually created breadcrumb navigation --> 30 | <nav class="breadcrumb"> 31 | <span>Home</span> / <span>Products</span> / <span>Details</span> 32 | </nav> 33 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/group-3/bad-mixed-external-assets-3.component.html: -------------------------------------------------------------------------------- ```html 1 | <!-- ❌ Bad: Using legacy button styles --> 2 | <button class="btn btn-primary">Legacy Button</button> 3 | 4 | <!-- ❌ Bad: Using custom modal structure --> 5 | <div class="modal"> 6 | <div class="modal-content"> 7 | <h2>Legacy Modal</h2> 8 | <p>This is a bad example of a modal.</p> 9 | </div> 10 | </div> 11 | 12 | <!-- ❌ Bad: Using legacy progress bar --> 13 | <div class="progress-bar"> 14 | <div class="progress" style="width: 50%"></div> 15 | </div> 16 | 17 | <!-- ❌ Bad: Hardcoded alert styles --> 18 | <div class="alert alert-danger">This is a legacy alert!</div> 19 | 20 | <!-- ❌ Bad: Using a custom dropdown instead of a component --> 21 | <select class="dropdown"> 22 | <option>Option 1</option> 23 | <option>Option 2</option> 24 | </select> 25 | 26 | <!-- ❌ Bad: Using a tooltip div instead of a component --> 27 | <div class="tooltip">Hover over me for info</div> 28 | 29 | <!-- ❌ Bad: Manually created breadcrumb navigation --> 30 | <nav class="breadcrumb"> 31 | <span>Home</span> / <span>Products</span> / <span>Details</span> 32 | </nav> 33 | ``` -------------------------------------------------------------------------------- /packages/shared/ds-component-coverage/mocks/fixtures/e2e/demo/src/mixed-external-assets.component.html: -------------------------------------------------------------------------------- ```html 1 | <!-- ❌ Bad: Using legacy button styles --> 2 | <button class="btn btn-primary">Legacy Button</button> 3 | 4 | <!-- ❌ Bad: Using custom modal structure --> 5 | <div class="modal"> 6 | <div class="modal-content"> 7 | <h2>Legacy Modal</h2> 8 | <p>This is a bad example of a modal.</p> 9 | </div> 10 | </div> 11 | 12 | <!-- ❌ Bad: Using legacy progress bar --> 13 | <div class="progress-bar"> 14 | <div class="progress" style="width: 50%"></div> 15 | </div> 16 | 17 | <!-- ❌ Bad: Hardcoded alert styles --> 18 | <div class="alert alert-danger">This is a legacy alert!</div> 19 | 20 | <!-- ❌ Bad: Using a custom dropdown instead of a component --> 21 | <select class="dropdown"> 22 | <option>Option 1</option> 23 | <option>Option 2</option> 24 | </select> 25 | 26 | <!-- ❌ Bad: Using a tooltip div instead of a component --> 27 | <div class="tooltip">Hover over me for info</div> 28 | 29 | <!-- ❌ Bad: Manually created breadcrumb navigation --> 30 | <nav class="breadcrumb"> 31 | <span>Home</span> / <span>Products</span> / <span>Details</span> 32 | </nav> 33 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/report-violations/models/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | BaseViolationOptions, 3 | BaseViolationResult, 4 | BaseViolationIssue, 5 | BaseViolationAudit, 6 | } from '../../shared/violation-analysis/types.js'; 7 | 8 | export interface ReportViolationsOptions extends BaseViolationOptions { 9 | groupBy?: 'file' | 'folder'; 10 | } 11 | 12 | export type ViolationResult = BaseViolationResult; 13 | export type ViolationIssue = BaseViolationIssue; 14 | export type ViolationAudit = BaseViolationAudit; 15 | 16 | // File-specific types (when groupBy: 'file') 17 | export interface FileViolation { 18 | fileName: string; 19 | message: string; 20 | lines: number[]; 21 | } 22 | 23 | export interface FileViolationGroup { 24 | message: string; 25 | lines: number[]; 26 | } 27 | 28 | export interface FileViolationGroups { 29 | [fileName: string]: FileViolationGroup; 30 | } 31 | 32 | // Folder-specific types (when groupBy: 'folder') 33 | export interface FolderViolationSummary { 34 | violations: number; 35 | files: string[]; 36 | } 37 | 38 | export interface FolderViolationGroups { 39 | [folderPath: string]: FolderViolationSummary; 40 | } 41 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/code-pushup.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { mergeConfigs } from '@code-pushup/utils'; 2 | import { dsComponentUsagePluginCoreConfig } from '../../../../../packages/shared/ds-component-coverage/src/core.config'; 3 | import { ssrAdoptionPluginCoreConfig } from '../../../../../src/ssr-adoption/src/core.config'; 4 | 5 | export default mergeConfigs( 6 | { 7 | persist: { 8 | outputDir: '.code-pushup/packages/application', 9 | format: ['json', 'md'], 10 | }, 11 | plugins: [], 12 | }, 13 | await ssrAdoptionPluginCoreConfig({ 14 | patterns: ['.'], 15 | eslintrc: 'plugins/src/ssr-adoption/src/eslint.config.ssr.cjs', 16 | }), 17 | await dsComponentUsagePluginCoreConfig({ 18 | directory: 'plugins/mocks/fixtures/minimal-repo/packages/application/src', 19 | dsComponents: [ 20 | { 21 | componentName: 'DSButton', 22 | deprecatedCssClasses: ['btn', 'btn-primary', 'legacy-button'], 23 | docsUrl: 24 | 'https://storybook.company.com/latest/?path=/docs/components-button--overview', 25 | }, 26 | ], 27 | }) 28 | ); 29 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/design-system/ui/rx-host-listener/src/rx-host-listener.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ChangeDetectorRef, ElementRef, inject } from '@angular/core'; 2 | import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; 3 | 4 | import { 5 | type Observable, 6 | debounceTime, 7 | filter, 8 | fromEvent, 9 | merge, 10 | of, 11 | switchMap, 12 | tap, 13 | } from 'rxjs'; 14 | 15 | export function rxHostListener<T extends Event>(event: string): Observable<T> { 16 | const cdr = inject(ChangeDetectorRef); 17 | 18 | // Listen to event 19 | return fromEvent<T>(inject(ElementRef).nativeElement, event).pipe( 20 | debounceTime(0), 21 | tap(() => cdr.markForCheck()), // Trigger CD like @HostListener would 22 | takeUntilDestroyed(), // Unsubscribe 23 | ); 24 | } 25 | 26 | /* 27 | * @internal 28 | **/ 29 | export function rxHostPressedListener(): Observable<any> { 30 | return merge( 31 | rxHostListener('click'), 32 | rxHostListener<KeyboardEvent>('keyup').pipe( 33 | switchMap((x) => { 34 | return x.code === 'Space' || x.code === 'Enter' ? of(true) : of(null); 35 | }), 36 | filter(Boolean), 37 | ), 38 | ).pipe(debounceTime(0)); 39 | } 40 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/validation-tests/invalid-pipe-usage.component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-invalid-pipe', 5 | standalone: true, 6 | template: ` 7 | <div> 8 | <h2>Invalid Pipe Usage Test</h2> 9 | <p>{{ text | nonExistentPipe }}</p> 10 | <div>{{ number | currency | }}</div> 11 | <span>{{ date | customPipe: 'param1' : 'param2' }}</span> 12 | <p>{{ value | | uppercase }}</p> 13 | <div>{{ items | slice:0:3 | unknownPipe }}</div> 14 | <span>{{ text | lowercase | missingPipe: }}</span> 15 | <p>{{ data | json | customFormat: param1, param2 }}</p> 16 | </div> 17 | `, 18 | styles: [` 19 | div { padding: 5px; } 20 | `] 21 | }) 22 | export class InvalidPipeUsageComponent { 23 | text = 'Hello World'; 24 | number = 123.45; 25 | date = new Date(); 26 | value = 'test value'; 27 | items = [1, 2, 3, 4, 5]; 28 | data = { name: 'test', value: 42 }; 29 | 30 | // Note: nonExistentPipe, customPipe, unknownPipe, missingPipe, customFormat don't exist 31 | // Note: malformed pipe syntax with empty pipes and missing parameters 32 | } ``` -------------------------------------------------------------------------------- /testing/utils/vite.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | /// <reference types='vitest' /> 2 | import { defineConfig } from 'vite'; 3 | import dts from 'vite-plugin-dts'; 4 | import * as path from 'path'; 5 | 6 | export default defineConfig({ 7 | root: __dirname, 8 | cacheDir: '../../node_modules/.vite/testing/testing-utils', 9 | plugins: [ 10 | dts({ 11 | entryRoot: 'src', 12 | tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), 13 | }), 14 | ], 15 | build: { 16 | outDir: './dist', 17 | emptyOutDir: true, 18 | reportCompressedSize: true, 19 | commonjsOptions: { 20 | transformMixedEsModules: true, 21 | }, 22 | lib: { 23 | // Could also be a dictionary or array of multiple entry points. 24 | entry: 'src/index.ts', 25 | name: 'testing-utils', 26 | fileName: 'index', 27 | // Change this to the formats you want to support. 28 | // Don't forget to update your package.json as well. 29 | formats: ['es'], 30 | }, 31 | rollupOptions: { 32 | // External packages that should not be bundled into your library. 33 | external: [], 34 | }, 35 | }, 36 | }); 37 | ``` -------------------------------------------------------------------------------- /packages/shared/ds-component-coverage/src/core.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { CategoryConfig, CoreConfig } from '@code-pushup/models'; 2 | import angularDsUsagePlugin, { 3 | DsComponentUsagePluginConfig, 4 | } from './lib/ds-component-coverage.plugin.js'; 5 | import { getAngularDsUsageCategoryRefs } from './lib/utils.js'; 6 | 7 | export async function dsComponentUsagePluginCoreConfig({ 8 | directory, 9 | dsComponents, 10 | }: DsComponentUsagePluginConfig): Promise<CoreConfig> { 11 | return { 12 | plugins: [ 13 | angularDsUsagePlugin({ 14 | directory, 15 | dsComponents, 16 | }), 17 | ], 18 | categories: await angularDsUsagePluginCategories({ dsComponents }), 19 | } as const satisfies CoreConfig; 20 | } 21 | 22 | export async function angularDsUsagePluginCategories({ 23 | dsComponents, 24 | }: Pick<DsComponentUsagePluginConfig, 'dsComponents'>): Promise< 25 | CategoryConfig[] 26 | > { 27 | return [ 28 | { 29 | slug: 'design-system-usage', 30 | title: 'Design System Usage', 31 | description: 'Usage of design system components', 32 | refs: getAngularDsUsageCategoryRefs(dsComponents), 33 | }, 34 | ]; 35 | } 36 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/shared/violation-analysis/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Shared violation analysis utilities 3 | * 4 | * This module provides shared functionality for analyzing design system violations 5 | * across different reporting formats (file and folder-based). 6 | */ 7 | 8 | // Core analysis 9 | export { analyzeViolationsBase } from './base-analyzer.js'; 10 | 11 | // Coverage analysis 12 | export { 13 | analyzeProjectCoverage, 14 | validateReportInput, 15 | executeCoveragePlugin, 16 | extractComponentName, 17 | formatCoverageResult, 18 | } from './coverage-analyzer.js'; 19 | 20 | // Formatting utilities 21 | export { 22 | filterFailedAudits, 23 | createNoViolationsContent, 24 | extractIssuesFromAudits, 25 | hasViolations, 26 | normalizeFilePath, 27 | normalizeMessage, 28 | groupIssuesByFile, 29 | extractUniqueFilePaths, 30 | clearPathCache, 31 | formatViolations, 32 | } from './formatters.js'; 33 | 34 | // Types 35 | export type { 36 | BaseViolationOptions, 37 | BaseViolationResult, 38 | BaseViolationAudit, 39 | BaseViolationIssue, 40 | ReportCoverageParams, 41 | DsComponent, 42 | FormattedCoverageResult, 43 | FileGroup, 44 | FileGroups, 45 | PathCache, 46 | } from './types.js'; 47 | ``` -------------------------------------------------------------------------------- /packages/shared/ds-component-coverage/src/lib/ds-component-coverage.plugin.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { PluginConfig } from '@code-pushup/models'; 2 | import { CreateRunnerConfig, runnerFunction } from './runner/create-runner.js'; 3 | import { getAudits } from './utils.js'; 4 | import { ANGULAR_DS_USAGE_PLUGIN_SLUG } from './constants.js'; 5 | 6 | export type DsComponentUsagePluginConfig = CreateRunnerConfig; 7 | 8 | /** 9 | * Plugin to measure and assert usage of DesignSystem components in an Angular project. 10 | * It will check if the there are class names in the project that should get replaced by a DesignSystem components. 11 | * 12 | * @param options 13 | */ 14 | export function dsComponentCoveragePlugin( 15 | options: DsComponentUsagePluginConfig, 16 | ): PluginConfig { 17 | return { 18 | slug: ANGULAR_DS_USAGE_PLUGIN_SLUG, 19 | title: 'Angular Design System Coverage', 20 | icon: 'angular', 21 | description: 22 | 'A plugin to measure and assert usage of styles in an Angular project.', 23 | audits: getAudits(options.dsComponents), 24 | runner: () => runnerFunction(options), 25 | } as const; 26 | } 27 | 28 | export default dsComponentCoveragePlugin; 29 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/validation-tests/template-reference-error.component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-template-reference-error', 5 | standalone: true, 6 | template: ` 7 | <div> 8 | <h2>Template Reference Error Test</h2> 9 | <input #inputRef type="text" /> 10 | <button (click)="processInput(nonExistentRef.value)">Process</button> 11 | <div>{{ undefinedTemplateVar }}</div> 12 | <span>{{ someRef.innerText }}</span> 13 | <p #paragraphRef>{{ missingMethod(paragraphRef.textContent) }}</p> 14 | <div *ngFor="let item of items; let i = index"> 15 | {{ item.name }} - {{ j }} - {{ unknownVar }} 16 | </div> 17 | </div> 18 | `, 19 | styles: [` 20 | div { margin: 8px; } 21 | `] 22 | }) 23 | export class TemplateReferenceErrorComponent { 24 | items = [{ name: 'test1' }, { name: 'test2' }]; 25 | 26 | processInput(value: string): void { 27 | console.log('Processing:', value); 28 | } 29 | 30 | // Note: missingMethod is called in template but not defined 31 | // Note: nonExistentRef, undefinedTemplateVar, someRef, j, unknownVar are referenced but don't exist 32 | } ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-contract/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | // Export shared types 2 | export type { 3 | ComponentContract, 4 | Meta, 5 | PublicApi, 6 | PropertyBinding, 7 | EventBinding, 8 | MethodSignature, 9 | ParameterInfo, 10 | ImportInfo, 11 | Slots, 12 | DomStructure, 13 | DomElement, 14 | Binding, 15 | Attribute, 16 | Event, 17 | StyleDeclarations, 18 | StyleRule, 19 | DomPathDictionary, 20 | TemplateType, 21 | } from './shared/models/types.js'; 22 | 23 | // Export module-specific types that external consumers might need 24 | export type { ContractResult } from './builder/models/types.js'; 25 | export type { ContractFileInfo } from './list/models/types.js'; 26 | 27 | // Export utilities and tools 28 | export { buildComponentContract } from './builder/utils/build-contract.js'; 29 | export { 30 | loadContract, 31 | saveContract, 32 | generateContractSummary, 33 | } from './shared/utils/contract-file-ops.js'; 34 | 35 | export { buildComponentContractTools } from './builder/build-component-contract.tool.js'; 36 | export { diffComponentContractTools } from './diff/diff-component-contract.tool.js'; 37 | export { listComponentContractsTools } from './list/list-component-contracts.tool.js'; 38 | ``` -------------------------------------------------------------------------------- /packages/shared/ds-component-coverage/src/lib/runner/schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from 'zod'; 2 | import { ComponentReplacementSchema } from './audits/ds-coverage/schema.js'; 3 | 4 | export const baseToolsSchema = { 5 | inputSchema: { 6 | type: 'object' as const, 7 | properties: { 8 | cwd: z 9 | .string({ description: 'The current working directory' }) 10 | .optional(), 11 | tsconfig: z 12 | .string({ 13 | description: 'The absolute path to a TypeScript config file', 14 | }) 15 | .optional(), 16 | verbose: z.boolean({ description: 'More verbose output' }).optional(), 17 | progress: z.boolean({ description: 'Show progress bar' }).optional(), 18 | }, 19 | required: [] as string[], 20 | }, 21 | }; 22 | 23 | export const ComponentCoverageRunnerOptionsSchema = z.object({ 24 | directory: z 25 | .string({ description: 'The directory to run the task in' }) 26 | .default('.'), 27 | dsComponents: z.array(ComponentReplacementSchema, { 28 | description: 'Array of components and their deprecated CSS classes', 29 | }), 30 | }); 31 | 32 | export type ComponentCoverageRunnerOptions = z.infer< 33 | typeof ComponentCoverageRunnerOptionsSchema 34 | >; 35 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-usage-graph/models/schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ToolSchemaOptions } from '@push-based/models'; 2 | 3 | export const buildComponentUsageGraphSchema: ToolSchemaOptions = { 4 | name: 'build-component-usage-graph', 5 | description: 6 | 'Maps where given Angular components are imported (modules, specs, templates, styles) so refactors touch every file.', 7 | inputSchema: { 8 | type: 'object', 9 | properties: { 10 | directory: { 11 | type: 'string', 12 | description: 13 | 'The relative path to the directory to analyze (starting with "./path/to/dir"). This should be the root directory containing the components with violations.', 14 | }, 15 | violationFiles: { 16 | type: 'array', 17 | items: { 18 | type: 'string', 19 | }, 20 | description: 21 | 'Array of file paths with design system violations (obtained from previous report-violations calls).', 22 | }, 23 | }, 24 | required: ['directory', 'violationFiles'], 25 | }, 26 | annotations: { 27 | title: 'Build Component Usage Graph', 28 | readOnlyHint: true, 29 | openWorldHint: false, 30 | idempotentHint: true, 31 | }, 32 | }; 33 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/report-violations/models/schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ToolSchemaOptions } from '@push-based/models'; 2 | 3 | export const reportViolationsSchema: ToolSchemaOptions = { 4 | name: 'report-violations', 5 | description: `Report deprecated DS CSS usage in a directory with configurable grouping format.`, 6 | inputSchema: { 7 | type: 'object', 8 | properties: { 9 | directory: { 10 | type: 'string', 11 | description: 12 | 'The relative path the directory (starting with "./path/to/dir" avoid big folders.) to run the task in starting from CWD. Respect the OS specifics.', 13 | }, 14 | componentName: { 15 | type: 'string', 16 | description: 17 | 'The class name of the component to search for (e.g., DsButton)', 18 | }, 19 | groupBy: { 20 | type: 'string', 21 | enum: ['file', 'folder'], 22 | description: 'How to group the violation results', 23 | default: 'file', 24 | }, 25 | }, 26 | required: ['directory', 'componentName'], 27 | }, 28 | annotations: { 29 | title: 'Report Violations', 30 | readOnlyHint: true, 31 | openWorldHint: false, 32 | idempotentHint: false, 33 | }, 34 | }; 35 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/validation-tests/type-mismatch.component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-type-mismatch', 5 | standalone: true, 6 | template: ` 7 | <div> 8 | <h2>Type Mismatch Test</h2> 9 | <input [value]="numberValue" type="text" /> 10 | <div [hidden]="stringFlag">Content</div> 11 | <button [disabled]="objectValue">Click me</button> 12 | <span [title]="arrayValue">Hover me</span> 13 | <p>{{ booleanValue.length }}</p> 14 | <div>{{ numberValue.toUpperCase() }}</div> 15 | <input [(ngModel)]="readonlyValue" /> 16 | </div> 17 | `, 18 | styles: [` 19 | div { padding: 10px; } 20 | `] 21 | }) 22 | export class TypeMismatchComponent { 23 | numberValue: number = 42; 24 | stringFlag: string = 'true'; // Should be boolean for [hidden] 25 | objectValue: { name: string } = { name: 'test' }; // Should be boolean for [disabled] 26 | arrayValue: string[] = ['a', 'b', 'c']; // Should be string for [title] 27 | booleanValue: boolean = true; // Calling .length on boolean 28 | readonly readonlyValue: string = 'readonly'; // Two-way binding on readonly 29 | 30 | // Note: Type mismatches will cause runtime errors and template binding issues 31 | } ``` -------------------------------------------------------------------------------- /testing/vitest-setup/src/lib/configuration.ts: -------------------------------------------------------------------------------- ```typescript 1 | export interface SharedVitestConfigOptions { 2 | projectRoot: string; 3 | workspaceRoot: string; 4 | environment?: 'node' | 'jsdom' | 'happy-dom'; 5 | include?: string[]; 6 | exclude?: string[]; 7 | testTimeout?: number; 8 | } 9 | 10 | export function createSharedVitestConfig(options: SharedVitestConfigOptions) { 11 | const { 12 | projectRoot, 13 | workspaceRoot, 14 | environment = 'node', 15 | include = ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], 16 | exclude = ['mocks/**', '**/types.ts', '**/__snapshots__/**'], 17 | testTimeout = 25_000, 18 | } = options; 19 | 20 | // Simple path joining for Node.js 21 | const projectName = projectRoot.split('/').slice(-2).join('-'); // e.g., "shared-utils" 22 | const coverageDir = `${workspaceRoot}/coverage/${projectName}`; 23 | 24 | return { 25 | test: { 26 | coverage: { 27 | provider: 'v8' as const, 28 | reporter: ['text', 'lcov'] as const, 29 | reportsDirectory: coverageDir, 30 | exclude, 31 | }, 32 | watch: false, 33 | globals: true, 34 | environment, 35 | include, 36 | reporters: ['default'] as const, 37 | passWithNoTests: true, 38 | testTimeout, 39 | }, 40 | }; 41 | } 42 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/validation-tests/wrong-decorator-usage.component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-wrong-decorator', 5 | standalone: true, 6 | template: ` 7 | <div> 8 | <h2>Wrong Decorator Usage Test</h2> 9 | <p>Input value: {{ inputValue }}</p> 10 | <button (click)="emitEvent()">Emit Event</button> 11 | </div> 12 | `, 13 | styles: [` 14 | div { padding: 12px; } 15 | `] 16 | }) 17 | export class WrongDecoratorUsageComponent { 18 | 19 | // Wrong: @Input on a method 20 | @Input() 21 | setInputValue(value: string): void { 22 | this.inputValue = value; 23 | } 24 | 25 | // Wrong: @Output on a regular property 26 | @Output() 27 | regularProperty = 'not an event emitter'; 28 | 29 | // Wrong: @Input with wrong type 30 | @Input() 31 | inputValue: EventEmitter<string> = new EventEmitter(); 32 | 33 | // Wrong: @Output with wrong type 34 | @Output() 35 | outputValue: string = 'should be EventEmitter'; 36 | 37 | // Wrong: Multiple decorators on same property 38 | @Input() 39 | @Output() 40 | conflictedProperty = 'has both decorators'; 41 | 42 | emitEvent(): void { 43 | // This will cause runtime error since outputValue is not EventEmitter 44 | this.outputValue.emit('test'); 45 | } 46 | } ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/validation-tests/invalid-lifecycle.component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Component, OnInit, OnDestroy, AfterViewInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-invalid-lifecycle', 5 | standalone: true, 6 | template: ` 7 | <div> 8 | <h2>Invalid Lifecycle Test</h2> 9 | <p>This component has incorrect lifecycle implementations</p> 10 | </div> 11 | `, 12 | styles: [` 13 | div { padding: 15px; } 14 | `] 15 | }) 16 | export class InvalidLifecycleComponent implements OnInit, OnDestroy, AfterViewInit { 17 | 18 | // Wrong signature - should be ngOnInit(): void 19 | ngOnInit(param: string): boolean { 20 | console.log('Wrong ngOnInit signature', param); 21 | return true; 22 | } 23 | 24 | // Wrong signature - should be ngOnDestroy(): void 25 | ngOnDestroy(cleanup: boolean): string { 26 | console.log('Wrong ngOnDestroy signature', cleanup); 27 | return 'destroyed'; 28 | } 29 | 30 | // Wrong signature - should be ngAfterViewInit(): void 31 | ngAfterViewInit(element: HTMLElement): Promise<void> { 32 | console.log('Wrong ngAfterViewInit signature', element); 33 | return Promise.resolve(); 34 | } 35 | 36 | // Method that doesn't exist in lifecycle 37 | ngOnChange(): void { 38 | console.log('This should be ngOnChanges'); 39 | } 40 | } ``` -------------------------------------------------------------------------------- /testing/utils/src/lib/execute-process.mock.mjs: -------------------------------------------------------------------------------- ``` 1 | const interval = parseInt(process.argv[2] || 100); 2 | let runs = parseInt(process.argv[3] || 4); 3 | const throwError = process.argv[4] === '1'; 4 | 5 | /** 6 | * Custom runner implementation that simulates asynchronous situations. 7 | * It logs progress to the console with a configurable interval and defaults to 100ms. 8 | * The number of runs is also configurable and defaults to 4. 9 | * We can decide if the process should error or complete. By default, it completes. 10 | * 11 | * @arg interval: number - delay between updates in ms; defaults to 100 12 | * @arg runs: number - number of updates; defaults to 4 13 | * @arg throwError: '1' | '0' - if the process completes or throws; defaults to '0' 14 | **/ 15 | console.info( 16 | `process:start with interval: ${interval}, runs: ${runs}, throwError: ${throwError}`, 17 | ); 18 | await new Promise((resolve) => { 19 | const id = setInterval(() => { 20 | if (runs === 0) { 21 | clearInterval(id); 22 | if (throwError) { 23 | throw new Error('dummy-error'); 24 | } else { 25 | resolve('result'); 26 | } 27 | } else { 28 | runs--; 29 | console.info('process:update'); 30 | } 31 | }, interval); 32 | }); 33 | 34 | console.info('process:complete'); 35 | ``` -------------------------------------------------------------------------------- /packages/shared/angular-ast-utils/src/lib/styles/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { Root } from 'postcss'; 2 | import { Issue } from '@code-pushup/models'; 3 | import { Asset, ParsedComponent } from '../types.js'; 4 | 5 | export async function visitComponentStyles<T>( 6 | component: ParsedComponent, 7 | visitorArgument: T, 8 | getIssues: (tokenReplacement: T, asset: Asset<Root>) => Promise<Issue[]>, 9 | ): Promise<(Issue & { code?: number })[]> { 10 | const { styles, styleUrls, styleUrl } = component; 11 | 12 | if (styleUrls == null && styles == null && styleUrl == null) { 13 | return []; 14 | } 15 | 16 | // Handle inline styles 17 | const styleIssues: Issue[] = ( 18 | await Promise.all( 19 | (styles ?? []).flatMap(async (style: Asset<Root>) => { 20 | return getIssues(visitorArgument, style); 21 | }), 22 | ) 23 | ).flat(); 24 | 25 | const styleUrlsIssues: Issue[] = ( 26 | await Promise.all( 27 | (styleUrls ?? []).flatMap(async (styleUrl: Asset<Root>) => { 28 | return getIssues(visitorArgument, styleUrl); 29 | }), 30 | ) 31 | ).flat(); 32 | 33 | const styleUrlIssues: Issue[] = styleUrl 34 | ? await (async () => { 35 | return getIssues(visitorArgument, styleUrl); 36 | })() 37 | : []; 38 | 39 | return [...styleIssues, ...styleUrlsIssues, ...styleUrlIssues]; 40 | } 41 | ``` -------------------------------------------------------------------------------- /packages/shared/angular-ast-utils/src/lib/ts.walk.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as ts from 'typescript'; 2 | import { getDecorators, isDecorator } from 'typescript'; 3 | 4 | /** 5 | * Visits all decorators in a given class. 6 | */ 7 | export function visitAngularDecorators( 8 | node: ts.Node, 9 | visitor: (decorator: ts.Decorator) => void, 10 | ) { 11 | const decorators = getDecorators(node as ts.HasDecorators); 12 | if (!decorators?.length) return; 13 | 14 | decorators.forEach((decorator: ts.Decorator) => { 15 | if (!isDecorator(decorator)) return; 16 | visitor(decorator); 17 | }); 18 | } 19 | 20 | // 👀 visit every decorator 21 | // ✔ in visitor use `isDecorator`or `isComponentDecorator` 22 | 23 | /** 24 | * Visits all properties inside a Angular decorator. e.g. @Component() 25 | */ 26 | export function visitAngularDecoratorProperties( 27 | decorator: ts.Decorator, 28 | visitor: (node: ts.PropertyAssignment) => void, 29 | ) { 30 | if (!ts.isCallExpression(decorator.expression)) { 31 | return; 32 | } 33 | const args = decorator.expression.arguments; 34 | if (!args?.length || !ts.isObjectLiteralExpression(args[0])) { 35 | return; 36 | } 37 | args[0].properties.forEach((prop: ts.ObjectLiteralElementLike) => { 38 | if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) return; 39 | visitor(prop); 40 | }); 41 | } 42 | ``` -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- ```yaml 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | permissions: 10 | actions: read 11 | contents: read 12 | 13 | jobs: 14 | main: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | # This enables task distribution via Nx Cloud 22 | # Run this command as early as possible, before dependencies are installed 23 | # Learn more at https://nx.dev/ci/reference/nx-cloud-cli#npx-nxcloud-startcirun 24 | # Uncomment this line to enable task distribution 25 | # - run: npx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="build" 26 | 27 | # Cache node_modules 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: 20 31 | cache: 'npm' 32 | 33 | - run: npm ci 34 | - uses: nrwl/nx-set-shas@v4 35 | - run: npx nx format:check 36 | 37 | # Prepend any command with "nx-cloud record --" to record its logs to Nx Cloud 38 | # - run: npx nx-cloud record -- echo Hello World 39 | # Nx Affected runs only tasks affected by the changes in this PR/commit. Learn more: https://nx.dev/ci/features/affected 40 | - run: npx nx affected -t lint test build 41 | ``` -------------------------------------------------------------------------------- /packages/shared/angular-ast-utils/ai/API.md: -------------------------------------------------------------------------------- ```markdown 1 | # Angular AST Utils 2 | 3 | Small, zero‑dependency helpers for **parsing and analysing Angular code** inside Model Context Protocol (MCP) tools. 4 | 5 | ## Minimal usage 6 | 7 | ```ts 8 | import { parseComponents } from 'angular-ast-utils'; 9 | 10 | const components = await parseComponents(['src/app/app.component.ts']); 11 | console.log(components[0].className); // → 'AppComponent' 12 | ``` 13 | 14 | ## Key Features 15 | 16 | - **Component Parsing**: Parse Angular components from TypeScript files 17 | - **Template Analysis**: Visit and analyze Angular template AST nodes 18 | - **Style Processing**: Process component styles and stylesheets 19 | - **Angular Unit Discovery**: Find components, directives, pipes, and services 20 | - **Decorator Traversal**: Walk through Angular decorators and their properties 21 | - **CSS Class Detection**: Check for CSS classes in `[ngClass]` bindings 22 | 23 | ## Documentation map 24 | 25 | | Doc | What you'll find | 26 | | ------------------------------ | ------------------------------------------- | 27 | | [FUNCTIONS.md](./FUNCTIONS.md) | A–Z quick reference for every public symbol | 28 | | [EXAMPLES.md](./EXAMPLES.md) | Runnable scenarios with expected output | 29 | ``` -------------------------------------------------------------------------------- /packages/shared/angular-ast-utils/src/lib/decorator-config.visitor.inline-styles.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | /* eslint-disable prefer-const */ 2 | import { describe, it, expect } from 'vitest'; 3 | import * as ts from 'typescript'; 4 | import { classDecoratorVisitor } from './decorator-config.visitor.js'; 5 | 6 | describe('DecoratorConfigVisitor - inline styles', () => { 7 | it('extracts inline styles when styles is provided as single string', async () => { 8 | const source = [ 9 | "import { Component } from '@angular/core';", 10 | '@Component({', 11 | " selector: 'x-comp',", 12 | ' styles: `', 13 | ' .btn { color: red; }', 14 | ' `', 15 | '})', 16 | 'export class XComponent {}', 17 | ].join('\n'); 18 | 19 | const sourceFile = ts.createSourceFile( 20 | 'cmp.ts', 21 | source, 22 | ts.ScriptTarget.Latest, 23 | true, 24 | ); 25 | 26 | const visitor = await classDecoratorVisitor({ sourceFile }); 27 | ts.visitEachChild(sourceFile, visitor, undefined); 28 | 29 | expect(visitor.components).toHaveLength(1); 30 | const cmp = visitor.components[0] as any; 31 | expect(cmp.styles).toBeDefined(); 32 | expect(Array.isArray(cmp.styles)).toBe(true); 33 | expect(cmp.styles.length).toBe(1); 34 | expect(cmp.styles[0]).toEqual( 35 | expect.objectContaining({ filePath: 'cmp.ts' }), 36 | ); 37 | }); 38 | }); 39 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-usage-graph/utils/path-resolver.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import { toUnixPath } from '@code-pushup/utils'; 4 | import { DEPENDENCY_ANALYSIS_CONFIG } from '../models/config.js'; 5 | 6 | const { resolveExtensions, indexFiles } = DEPENDENCY_ANALYSIS_CONFIG; 7 | 8 | const SUFFIXES = ['', ...resolveExtensions, ...indexFiles.map((ext) => ext)]; 9 | 10 | // Memoized fs.existsSync (tiny utility) 11 | const existsCache = new Map<string, boolean>(); 12 | const exists = (p: string): boolean => { 13 | const cached = existsCache.get(p); 14 | if (cached !== undefined) return cached; 15 | const ok = fs.existsSync(p); 16 | existsCache.set(p, ok); 17 | return ok; 18 | }; 19 | 20 | export const isExternal = (p: string): boolean => 21 | !p.startsWith('./') && !p.startsWith('../') && !path.isAbsolute(p); 22 | 23 | export const resolveDependencyPath = ( 24 | importPath: string, 25 | fromFile: string, 26 | basePath: string, 27 | ): string | null => { 28 | if (isExternal(importPath)) return null; 29 | 30 | const base = path.resolve( 31 | path.dirname(fromFile), 32 | importPath.replace(/\/$/, ''), 33 | ); 34 | for (const s of SUFFIXES) { 35 | const candidate = base + s; 36 | if (exists(candidate)) { 37 | return toUnixPath(path.relative(basePath, candidate)); 38 | } 39 | } 40 | return null; 41 | }; 42 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp/webpack.config.cjs: -------------------------------------------------------------------------------- ``` 1 | const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin'); 2 | const { join } = require('path'); 3 | const webpack = require('webpack'); 4 | 5 | module.exports = { 6 | output: { 7 | path: join(__dirname, 'dist'), 8 | }, 9 | resolve: { 10 | extensions: ['.ts', '.js', '.mjs', '.cjs', '.json'], 11 | }, 12 | externals: [ 13 | // Keep Node.js built-ins external 14 | function ({ request }, callback) { 15 | if (/^node:/.test(request)) { 16 | return callback(null, 'commonjs ' + request); 17 | } 18 | callback(); 19 | }, 20 | ], 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.d\.ts$/, 25 | loader: 'ignore-loader', 26 | }, 27 | ], 28 | }, 29 | plugins: [ 30 | new NxAppWebpackPlugin({ 31 | target: 'node', 32 | compiler: 'tsc', 33 | main: './src/main.ts', 34 | tsConfig: './tsconfig.app.json', 35 | assets: [ 36 | './src/assets', 37 | { 38 | input: '.', 39 | glob: 'README.md', 40 | output: '.', 41 | }, 42 | ], 43 | optimization: false, 44 | outputHashing: 'none', 45 | generatePackageJson: true, 46 | externalDependencies: 'none', 47 | }), 48 | new webpack.BannerPlugin({ 49 | banner: '#!/usr/bin/env node', 50 | raw: true, 51 | entryOnly: true, 52 | }), 53 | ], 54 | }; 55 | ``` -------------------------------------------------------------------------------- /testing/vitest-setup/vite.config.ts: -------------------------------------------------------------------------------- ```typescript 1 | /// <reference types='vitest' /> 2 | import { defineConfig } from 'vite'; 3 | import dts from 'vite-plugin-dts'; 4 | import * as path from 'path'; 5 | 6 | export default defineConfig({ 7 | root: __dirname, 8 | cacheDir: '../../node_modules/.vite/testing/testing-vitest-setup', 9 | plugins: [ 10 | dts({ 11 | entryRoot: 'src', 12 | tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'), 13 | }), 14 | ], 15 | // Uncomment this if you are using workers. 16 | // worker: { 17 | // plugins: [ nxViteTsPaths() ], 18 | // }, 19 | // Configuration for building your library. 20 | // See: https://vitejs.dev/guide/build.html#library-mode 21 | build: { 22 | outDir: './dist', 23 | emptyOutDir: true, 24 | reportCompressedSize: true, 25 | commonjsOptions: { 26 | transformMixedEsModules: true, 27 | }, 28 | lib: { 29 | // Could also be a dictionary or array of multiple entry points. 30 | entry: 'src/index.ts', 31 | name: 'testing-vitest-setup', 32 | fileName: 'index', 33 | // Change this to the formats you want to support. 34 | // Don't forget to update your package.json as well. 35 | formats: ['es'], 36 | }, 37 | rollupOptions: { 38 | // External packages that should not be bundled into your library. 39 | external: [], 40 | }, 41 | }, 42 | }); 43 | ``` -------------------------------------------------------------------------------- /packages/shared/typescript-ast-utils/src/lib/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as ts from 'typescript'; 2 | 3 | import { QUOTE_REGEX } from './constants.js'; 4 | 5 | export function isComponentDecorator(decorator: ts.Decorator): boolean { 6 | return isDecorator(decorator, 'Component'); 7 | } 8 | 9 | export function getDecorators(node: ts.Node): readonly ts.Decorator[] { 10 | if (ts.getDecorators) { 11 | return ts.getDecorators(node as ts.HasDecorators) ?? []; 12 | } 13 | if (hasDecorators(node)) { 14 | return node.decorators; 15 | } 16 | return []; 17 | } 18 | 19 | export function isDecorator( 20 | decorator: ts.Decorator, 21 | decoratorName?: string, 22 | ): boolean { 23 | const nodeObject = decorator?.expression as unknown as { 24 | expression: ts.Expression; 25 | }; 26 | const identifierObject = nodeObject?.expression; 27 | 28 | if (identifierObject == null || !ts.isIdentifier(identifierObject)) 29 | return false; 30 | 31 | if (decoratorName == null) { 32 | return true; 33 | } 34 | return identifierObject.text === decoratorName; 35 | } 36 | 37 | export function removeQuotes(node: ts.Node, sourceFile: ts.SourceFile): string { 38 | return node.getText(sourceFile).replace(QUOTE_REGEX, ''); 39 | } 40 | 41 | export function hasDecorators( 42 | node: ts.Node, 43 | ): node is ts.Node & { decorators: readonly ts.Decorator[] } { 44 | return 'decorators' in node && Array.isArray(node.decorators); 45 | } 46 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/bad-mixed-external-assets.component.css: -------------------------------------------------------------------------------- ```css 1 | /* ❌ Bad: Legacy button styles */ 2 | .btn { 3 | padding: 10px 15px; 4 | background-color: blue; 5 | color: white; 6 | border: none; 7 | border-radius: 5px; 8 | cursor: pointer; 9 | } 10 | 11 | /* ❌ Bad: Custom modal styles */ 12 | .modal { 13 | position: fixed; 14 | top: 50%; 15 | left: 50%; 16 | transform: translate(-50%, -50%); 17 | background: white; 18 | padding: 20px; 19 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); 20 | } 21 | 22 | .modal-content { 23 | text-align: center; 24 | } 25 | 26 | /* ❌ Bad: Custom progress bar */ 27 | .progress-bar { 28 | width: 100%; 29 | background-color: #ddd; 30 | } 31 | 32 | .progress { 33 | height: 10px; 34 | background-color: green; 35 | } 36 | 37 | /* ❌ Bad: Legacy alert */ 38 | .alert { 39 | padding: 10px; 40 | border: 1px solid red; 41 | background-color: pink; 42 | } 43 | 44 | /* ❌ Bad: Hardcoded dropdown */ 45 | .dropdown { 46 | padding: 5px; 47 | border: 1px solid #ccc; 48 | background-color: white; 49 | } 50 | 51 | /* ❌ Bad: Custom tooltip */ 52 | .tooltip { 53 | position: relative; 54 | display: inline-block; 55 | cursor: pointer; 56 | } 57 | 58 | .tooltip:hover::after { 59 | content: 'This is a tooltip'; 60 | position: absolute; 61 | background: black; 62 | color: white; 63 | padding: 5px; 64 | border-radius: 5px; 65 | top: 100%; 66 | left: 50%; 67 | transform: translateX(-50%); 68 | } 69 | 70 | /* ❌ Bad: Breadcrumb styling */ 71 | .breadcrumb { 72 | display: flex; 73 | gap: 5px; 74 | } 75 | 76 | .breadcrumb span { 77 | color: blue; 78 | cursor: pointer; 79 | } 80 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/group-1/bad-mixed-external-assets-1.component.css: -------------------------------------------------------------------------------- ```css 1 | /* ❌ Bad: Legacy button styles */ 2 | .btn { 3 | padding: 10px 15px; 4 | background-color: blue; 5 | color: white; 6 | border: none; 7 | border-radius: 5px; 8 | cursor: pointer; 9 | } 10 | 11 | /* ❌ Bad: Custom modal styles */ 12 | .modal { 13 | position: fixed; 14 | top: 50%; 15 | left: 50%; 16 | transform: translate(-50%, -50%); 17 | background: white; 18 | padding: 20px; 19 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); 20 | } 21 | 22 | .modal-content { 23 | text-align: center; 24 | } 25 | 26 | /* ❌ Bad: Custom progress bar */ 27 | .progress-bar { 28 | width: 100%; 29 | background-color: #ddd; 30 | } 31 | 32 | .progress { 33 | height: 10px; 34 | background-color: green; 35 | } 36 | 37 | /* ❌ Bad: Legacy alert */ 38 | .alert { 39 | padding: 10px; 40 | border: 1px solid red; 41 | background-color: pink; 42 | } 43 | 44 | /* ❌ Bad: Hardcoded dropdown */ 45 | .dropdown { 46 | padding: 5px; 47 | border: 1px solid #ccc; 48 | background-color: white; 49 | } 50 | 51 | /* ❌ Bad: Custom tooltip */ 52 | .tooltip { 53 | position: relative; 54 | display: inline-block; 55 | cursor: pointer; 56 | } 57 | 58 | .tooltip:hover::after { 59 | content: 'This is a tooltip'; 60 | position: absolute; 61 | background: black; 62 | color: white; 63 | padding: 5px; 64 | border-radius: 5px; 65 | top: 100%; 66 | left: 50%; 67 | transform: translateX(-50%); 68 | } 69 | 70 | /* ❌ Bad: Breadcrumb styling */ 71 | .breadcrumb { 72 | display: flex; 73 | gap: 5px; 74 | } 75 | 76 | .breadcrumb span { 77 | color: blue; 78 | cursor: pointer; 79 | } 80 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/group-2/bad-mixed-external-assets-2.component.css: -------------------------------------------------------------------------------- ```css 1 | /* ❌ Bad: Legacy button styles */ 2 | .btn { 3 | padding: 10px 15px; 4 | background-color: blue; 5 | color: white; 6 | border: none; 7 | border-radius: 5px; 8 | cursor: pointer; 9 | } 10 | 11 | /* ❌ Bad: Custom modal styles */ 12 | .modal { 13 | position: fixed; 14 | top: 50%; 15 | left: 50%; 16 | transform: translate(-50%, -50%); 17 | background: white; 18 | padding: 20px; 19 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); 20 | } 21 | 22 | .modal-content { 23 | text-align: center; 24 | } 25 | 26 | /* ❌ Bad: Custom progress bar */ 27 | .progress-bar { 28 | width: 100%; 29 | background-color: #ddd; 30 | } 31 | 32 | .progress { 33 | height: 10px; 34 | background-color: green; 35 | } 36 | 37 | /* ❌ Bad: Legacy alert */ 38 | .alert { 39 | padding: 10px; 40 | border: 1px solid red; 41 | background-color: pink; 42 | } 43 | 44 | /* ❌ Bad: Hardcoded dropdown */ 45 | .dropdown { 46 | padding: 5px; 47 | border: 1px solid #ccc; 48 | background-color: white; 49 | } 50 | 51 | /* ❌ Bad: Custom tooltip */ 52 | .tooltip { 53 | position: relative; 54 | display: inline-block; 55 | cursor: pointer; 56 | } 57 | 58 | .tooltip:hover::after { 59 | content: 'This is a tooltip'; 60 | position: absolute; 61 | background: black; 62 | color: white; 63 | padding: 5px; 64 | border-radius: 5px; 65 | top: 100%; 66 | left: 50%; 67 | transform: translateX(-50%); 68 | } 69 | 70 | /* ❌ Bad: Breadcrumb styling */ 71 | .breadcrumb { 72 | display: flex; 73 | gap: 5px; 74 | } 75 | 76 | .breadcrumb span { 77 | color: blue; 78 | cursor: pointer; 79 | } 80 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/group-3/bad-mixed-external-assets-3.component.css: -------------------------------------------------------------------------------- ```css 1 | /* ❌ Bad: Legacy button styles */ 2 | .btn { 3 | padding: 10px 15px; 4 | background-color: blue; 5 | color: white; 6 | border: none; 7 | border-radius: 5px; 8 | cursor: pointer; 9 | } 10 | 11 | /* ❌ Bad: Custom modal styles */ 12 | .modal { 13 | position: fixed; 14 | top: 50%; 15 | left: 50%; 16 | transform: translate(-50%, -50%); 17 | background: white; 18 | padding: 20px; 19 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); 20 | } 21 | 22 | .modal-content { 23 | text-align: center; 24 | } 25 | 26 | /* ❌ Bad: Custom progress bar */ 27 | .progress-bar { 28 | width: 100%; 29 | background-color: #ddd; 30 | } 31 | 32 | .progress { 33 | height: 10px; 34 | background-color: green; 35 | } 36 | 37 | /* ❌ Bad: Legacy alert */ 38 | .alert { 39 | padding: 10px; 40 | border: 1px solid red; 41 | background-color: pink; 42 | } 43 | 44 | /* ❌ Bad: Hardcoded dropdown */ 45 | .dropdown { 46 | padding: 5px; 47 | border: 1px solid #ccc; 48 | background-color: white; 49 | } 50 | 51 | /* ❌ Bad: Custom tooltip */ 52 | .tooltip { 53 | position: relative; 54 | display: inline-block; 55 | cursor: pointer; 56 | } 57 | 58 | .tooltip:hover::after { 59 | content: 'This is a tooltip'; 60 | position: absolute; 61 | background: black; 62 | color: white; 63 | padding: 5px; 64 | border-radius: 5px; 65 | top: 100%; 66 | left: 50%; 67 | transform: translateX(-50%); 68 | } 69 | 70 | /* ❌ Bad: Breadcrumb styling */ 71 | .breadcrumb { 72 | display: flex; 73 | gap: 5px; 74 | } 75 | 76 | .breadcrumb span { 77 | color: blue; 78 | cursor: pointer; 79 | } 80 | ``` -------------------------------------------------------------------------------- /packages/shared/ds-component-coverage/mocks/fixtures/e2e/demo/src/mixed-external-assets.component.css: -------------------------------------------------------------------------------- ```css 1 | /* ❌ Bad: Legacy button styles */ 2 | .btn { 3 | padding: 10px 15px; 4 | background-color: blue; 5 | color: white; 6 | border: none; 7 | border-radius: 5px; 8 | cursor: pointer; 9 | } 10 | 11 | /* ❌ Bad: Custom modal styles */ 12 | .modal { 13 | position: fixed; 14 | top: 50%; 15 | left: 50%; 16 | transform: translate(-50%, -50%); 17 | background: white; 18 | padding: 20px; 19 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); 20 | } 21 | 22 | .modal-content { 23 | text-align: center; 24 | } 25 | 26 | /* ❌ Bad: Custom progress bar */ 27 | .progress-bar { 28 | width: 100%; 29 | background-color: #ddd; 30 | } 31 | 32 | .progress { 33 | height: 10px; 34 | background-color: green; 35 | } 36 | 37 | /* ❌ Bad: Legacy alert */ 38 | .alert { 39 | padding: 10px; 40 | border: 1px solid red; 41 | background-color: pink; 42 | } 43 | 44 | /* ❌ Bad: Hardcoded dropdown */ 45 | .dropdown { 46 | padding: 5px; 47 | border: 1px solid #ccc; 48 | background-color: white; 49 | } 50 | 51 | /* ❌ Bad: Custom tooltip */ 52 | .tooltip { 53 | position: relative; 54 | display: inline-block; 55 | cursor: pointer; 56 | } 57 | 58 | .tooltip:hover::after { 59 | content: 'This is a tooltip'; 60 | position: absolute; 61 | background: black; 62 | color: white; 63 | padding: 5px; 64 | border-radius: 5px; 65 | top: 100%; 66 | left: 50%; 67 | transform: translateX(-50%); 68 | } 69 | 70 | /* ❌ Bad: Breadcrumb styling */ 71 | .breadcrumb { 72 | display: flex; 73 | gap: 5px; 74 | } 75 | 76 | .breadcrumb span { 77 | color: blue; 78 | cursor: pointer; 79 | } 80 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/design-system/ui/modal/src/modal.component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | ChangeDetectionStrategy, 3 | Component, 4 | Signal, 5 | ViewEncapsulation, 6 | booleanAttribute, 7 | computed, 8 | inject, 9 | input, 10 | signal, 11 | } from '@angular/core'; 12 | 13 | export const DS_MODAL_VARIANT_ARRAY = [ 14 | 'surface-lowest', 15 | 'surface-low', 16 | 'surface', 17 | ] as const; 18 | export type DsModalVariant = (typeof DS_MODAL_VARIANT_ARRAY)[number]; 19 | export class DsModalContext { 20 | inverse: Signal<boolean> = signal(false); // Explicit type 21 | } 22 | 23 | @Component({ 24 | selector: 'ds-modal', 25 | template: `<ng-content />`, 26 | host: { 27 | class: 'ds-modal', 28 | role: 'dialog', 29 | 'aria-label': 'Modal dialog', 30 | '[class.ds-modal-inverse]': 'inverse()', 31 | '[class.ds-modal-bottom-sheet]': 'bottomSheet()', 32 | '[class]': 'hostClass()', 33 | }, 34 | providers: [DsModalContext], 35 | standalone: true, 36 | encapsulation: ViewEncapsulation.None, 37 | changeDetection: ChangeDetectionStrategy.OnPush, 38 | }) 39 | export class DsModal { 40 | inverse = input(false, { transform: booleanAttribute }); 41 | bottomSheet = input(false, { transform: booleanAttribute }); 42 | variant = input<DsModalVariant>('surface'); 43 | 44 | private context = inject(DsModalContext); 45 | 46 | protected hostClass = computed(() => `ds-modal-${this.variant()}`); 47 | 48 | constructor() { 49 | this.context.inverse = computed(() => this.inverse()); 50 | } 51 | } 52 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/tools.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ToolsConfig } from '@push-based/models'; 2 | import { 3 | reportViolationsTools, 4 | reportAllViolationsTools, 5 | } from './report-violations/index.js'; 6 | import { getProjectDependenciesTools } from './project/get-project-dependencies.tool.js'; 7 | import { reportDeprecatedCssTools } from './project/report-deprecated-css.tool.js'; 8 | import { buildComponentUsageGraphTools } from './component-usage-graph/index.js'; 9 | import { getDsComponentDataTools } from './component/get-ds-component-data.tool.js'; 10 | import { listDsComponentsTools } from './component/list-ds-components.tool.js'; 11 | import { getDeprecatedCssClassesTools } from './component/get-deprecated-css-classes.tool.js'; 12 | import { 13 | buildComponentContractTools, 14 | diffComponentContractTools, 15 | listComponentContractsTools, 16 | } from './component-contract/index.js'; 17 | 18 | export const dsTools: ToolsConfig[] = [ 19 | // Project tools 20 | ...reportViolationsTools, 21 | ...reportAllViolationsTools, 22 | ...getProjectDependenciesTools, 23 | ...reportDeprecatedCssTools, 24 | ...buildComponentUsageGraphTools, 25 | // Component contract tools 26 | ...buildComponentContractTools, 27 | ...diffComponentContractTools, 28 | ...listComponentContractsTools, 29 | // Component tools 30 | ...getDsComponentDataTools, 31 | ...listDsComponentsTools, 32 | ...getDeprecatedCssClassesTools, 33 | ]; 34 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/prompts/prompt-registry.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { PromptSchema } from '@modelcontextprotocol/sdk/types.js'; 2 | import { z } from 'zod'; 3 | 4 | /** 5 | * Registry of available prompts for the Angular MCP server. 6 | * 7 | * This registry is currently empty but provides the infrastructure 8 | * for future prompt implementations. Prompts allow LLMs to request 9 | * structured text generation with specific parameters. 10 | * 11 | * When adding prompts: 12 | * 1. Add the prompt schema to PROMPTS with name, description, and arguments 13 | * 2. Add the corresponding implementation to PROMPTS_IMPL 14 | * 15 | * Example implementation: 16 | * ```typescript 17 | * PROMPTS['component-docs'] = { 18 | * name: 'component-docs', 19 | * description: 'Generate documentation for Angular components', 20 | * arguments: [ 21 | * { 22 | * name: 'componentName', 23 | * description: 'Name of the component to document', 24 | * required: true, 25 | * }, 26 | * ], 27 | * }; 28 | * 29 | * PROMPTS_IMPL['component-docs'] = { 30 | * text: (args) => `Generate docs for ${args.componentName}...`, 31 | * }; 32 | * ``` 33 | */ 34 | export const PROMPTS: Record<string, z.infer<typeof PromptSchema>> = { 35 | // Future prompts will be added here 36 | } as const; 37 | 38 | export const PROMPTS_IMPL: Record< 39 | string, 40 | { text: (args: Record<string, string>) => string } 41 | > = { 42 | // Future prompt implementations will be added here 43 | } as const; 44 | ``` -------------------------------------------------------------------------------- /tools/nx-advanced-profile.bin.js: -------------------------------------------------------------------------------- ```javascript 1 | import { mkdirSync, writeFileSync } from 'node:fs'; 2 | import { parseArgs } from 'node:util'; 3 | import { nxRunWithPerfLogging } from './nx-advanced-profile.js'; 4 | 5 | const { values } = parseArgs({ 6 | options: { 7 | args: { 8 | type: 'string', 9 | }, 10 | verbose: { 11 | type: 'boolean', 12 | short: 'v', 13 | }, 14 | noPatch: { 15 | type: 'boolean', 16 | short: 'p', 17 | }, 18 | outDir: { 19 | type: 'string', 20 | short: 'd', 21 | }, 22 | outFile: { 23 | type: 'string', 24 | short: 'f', 25 | }, 26 | }, 27 | }); 28 | 29 | const { 30 | args = ['show', 'projects'].join(','), 31 | verbose, 32 | noPatch, 33 | outDir = '.nx-profiling', 34 | outFile = `nx-${args.split(',').join('-')}.${Date.now()}.profile.json`, 35 | } = values; 36 | 37 | // Run the function with arguments and write the collected timings to a JSON file. 38 | nxRunWithPerfLogging(args.split(','), { 39 | verbose, 40 | noPatch, 41 | onData: (perfProfileEvent) => { 42 | // console.log(perfProfileEvent); 43 | }, 44 | beforeExit: (profile) => { 45 | // @TODO figure out why profile directly does not show the flames but profile.traceEvents does 46 | const profileStdout = JSON.stringify(profile.traceEvents, null, 2); 47 | mkdirSync(outDir, { recursive: true }); 48 | writeFileSync(`${outDir}/${outFile}`, profileStdout); 49 | if (verbose) { 50 | console.log(profileStdout); 51 | } 52 | }, 53 | }); 54 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | export type BaseToolSchema = { 2 | cwd: string; 3 | }; 4 | 5 | export type ToolInput< 6 | TSchema extends Record< 7 | string, 8 | { inputSchema: { properties: Record<string, unknown> } } 9 | >, 10 | T extends keyof TSchema, 11 | > = { 12 | [K in keyof TSchema[T]['inputSchema']['properties']]: TSchema[T]['inputSchema']['properties'][K] extends { 13 | type: 'string'; 14 | } 15 | ? string 16 | : TSchema[T]['inputSchema']['properties'][K] extends { 17 | type: 'array'; 18 | items: { type: 'string' }; 19 | } 20 | ? string[] 21 | : TSchema[T]['inputSchema']['properties'][K] extends { 22 | type: 'array'; 23 | items: { type: 'object'; properties: Record<string, unknown> }; 24 | } 25 | ? Array<{ 26 | [P in keyof TSchema[T]['inputSchema']['properties'][K]['items']['properties']]: TSchema[T]['inputSchema']['properties'][K]['items']['properties'][P] extends { 27 | type: 'string'; 28 | } 29 | ? string 30 | : TSchema[T]['inputSchema']['properties'][K]['items']['properties'][P] extends { 31 | type: 'array'; 32 | items: { type: 'string' }; 33 | } 34 | ? string[] 35 | : never; 36 | }> 37 | : never; 38 | }; 39 | export type NamedRecord<T extends string> = Record<T, { name: T }>; 40 | ``` -------------------------------------------------------------------------------- /packages/shared/models/ai/API.md: -------------------------------------------------------------------------------- ```markdown 1 | # Models 2 | 3 | Simple **TypeScript types** for Angular MCP toolkit shared interfaces and utilities. 4 | 5 | ## Minimal usage 6 | 7 | ```ts 8 | import { type CliArgsObject, type ToolsConfig } from '@push-based/models'; 9 | 10 | // CLI argument types 11 | const args: CliArgsObject = { 12 | directory: './src', 13 | componentName: 'DsButton', 14 | _: ['command'], 15 | }; 16 | 17 | // MCP tool configuration 18 | const toolConfig: ToolsConfig = { 19 | schema: { 20 | name: 'my-tool', 21 | description: 'A custom tool', 22 | inputSchema: { 23 | type: 'object', 24 | properties: {}, 25 | }, 26 | }, 27 | handler: async (request) => { 28 | return { content: [{ type: 'text', text: 'Result' }] }; 29 | }, 30 | }; 31 | ``` 32 | 33 | ## Key Features 34 | 35 | - **CLI Types**: Type-safe command line argument handling 36 | - **MCP Integration**: Model Context Protocol tool schema definitions and handlers 37 | - **Diagnostics**: Interface for objects that can report issues and diagnostics 38 | - **Lightweight**: Minimal dependencies, focused on essential shared types 39 | 40 | ## Documentation map 41 | 42 | | Doc | What you'll find | 43 | | ------------------------------ | ------------------------------------------- | 44 | | [FUNCTIONS.md](./FUNCTIONS.md) | A–Z quick reference for every public symbol | 45 | | [EXAMPLES.md](./EXAMPLES.md) | Runnable scenarios with expected output | 46 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/design-system/ui/segmented-control/src/segmented-option.component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | ChangeDetectionStrategy, 3 | Component, 4 | TemplateRef, 5 | ViewEncapsulation, 6 | computed, 7 | contentChild, 8 | inject, 9 | input, 10 | output, 11 | } from '@angular/core'; 12 | import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; 13 | 14 | import { rxHostPressedListener } from '@frontend/ui/rx-host-listener'; 15 | 16 | import { DsSegmentedControl } from './segmented-control.component'; 17 | 18 | @Component({ 19 | selector: 'ds-segmented-option', 20 | template: `<ng-content />`, 21 | standalone: true, 22 | encapsulation: ViewEncapsulation.None, 23 | changeDetection: ChangeDetectionStrategy.OnPush, 24 | }) 25 | export class DsSegmentedOption { 26 | private segmentedControl = inject(DsSegmentedControl); 27 | 28 | readonly title = input(''); 29 | readonly name = input.required<string>(); 30 | 31 | readonly selectOption = output<string>(); 32 | 33 | readonly customTemplate = contentChild<TemplateRef<any>>('dsTemplate'); 34 | 35 | readonly selected = computed( 36 | () => this.segmentedControl.selectedOption() === this, 37 | ); 38 | readonly focusVisible = computed( 39 | () => this.segmentedControl.focusVisibleOption() === this, 40 | ); 41 | readonly focused = computed( 42 | () => this.segmentedControl.focusedOption() === this, 43 | ); 44 | 45 | constructor() { 46 | rxHostPressedListener() 47 | .pipe(takeUntilDestroyed()) 48 | .subscribe(() => this.selectOption.emit(this.name())); 49 | } 50 | } 51 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-contract/diff/models/schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ToolSchemaOptions } from '@push-based/models'; 2 | import { COMMON_ANNOTATIONS } from '../../../shared/index.js'; 3 | 4 | /** 5 | * Schema for diffing component contracts 6 | */ 7 | export const diffComponentContractSchema: ToolSchemaOptions = { 8 | name: 'diff_component_contract', 9 | description: 10 | 'Compare before/after contracts for parity and surface breaking changes.', 11 | inputSchema: { 12 | type: 'object', 13 | properties: { 14 | saveLocation: { 15 | type: 'string', 16 | description: 17 | 'Path where to save the diff result file. Supports both absolute and relative paths.', 18 | }, 19 | contractBeforePath: { 20 | type: 'string', 21 | description: 22 | 'Path to the contract file before refactoring. Supports both absolute and relative paths.', 23 | }, 24 | contractAfterPath: { 25 | type: 'string', 26 | description: 27 | 'Path to the contract file after refactoring. Supports both absolute and relative paths.', 28 | }, 29 | dsComponentName: { 30 | type: 'string', 31 | description: 'The name of the design system component being used', 32 | default: '', 33 | }, 34 | }, 35 | required: ['saveLocation', 'contractBeforePath', 'contractAfterPath'], 36 | }, 37 | annotations: { 38 | title: 'Diff Component Contract', 39 | ...COMMON_ANNOTATIONS.readOnly, 40 | }, 41 | }; 42 | ``` -------------------------------------------------------------------------------- /packages/shared/angular-ast-utils/src/lib/parse-component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { toUnixPath } from '@code-pushup/utils'; 2 | import * as ts from 'typescript'; 3 | 4 | import { classDecoratorVisitor } from './decorator-config.visitor.js'; 5 | import { ParsedComponent } from './types.js'; 6 | 7 | /** 8 | * Parses Angular components from a `FastFindInFiles` result. 9 | * It uses `typescript` to parse the components source files and extract the decorators. 10 | * From the decorators, it extracts the `@Component` decorator and its properties. 11 | * The used properties are `templateUrl`, `template`, `styles`, and `styleUrls`. 12 | * 13 | * @param files 14 | */ 15 | export async function parseComponents( 16 | files: string[], 17 | ): Promise<ParsedComponent[]> { 18 | const filePaths = new Set(files.map((filePath) => toUnixPath(filePath))); 19 | 20 | const program = ts.createProgram([...filePaths], { 21 | target: ts.ScriptTarget.Latest, 22 | module: ts.ModuleKind.ESNext, 23 | experimentalDecorators: true, 24 | }); 25 | 26 | const sourceFiles: ts.SourceFile[] = program 27 | .getSourceFiles() 28 | .filter((file: ts.SourceFile) => filePaths.has(file.fileName)); 29 | 30 | const results: ParsedComponent[] = []; 31 | //sourceFiles 32 | for (const sourceFile of sourceFiles) { 33 | const visitor = await classDecoratorVisitor({ sourceFile }); 34 | 35 | ts.visitEachChild(sourceFile, visitor, undefined); 36 | results.push(...visitor.components); 37 | } 38 | 39 | return results; 40 | } 41 | ``` -------------------------------------------------------------------------------- /packages/shared/styles-ast-utils/ai/API.md: -------------------------------------------------------------------------------- ```markdown 1 | # Styles AST Utils 2 | 3 | Small, zero‑dependency helpers for **parsing and analyzing CSS/SCSS stylesheets** using PostCSS inside Model Context Protocol (MCP) tools. 4 | 5 | ## Minimal usage 6 | 7 | ```ts 8 | import { parseStylesheet, visitEachChild } from '@push-based/styles-ast-utils'; 9 | 10 | const result = parseStylesheet('.btn { color: red; }', 'styles.css'); 11 | const visitor = { 12 | visitRule: (rule) => console.log(rule.selector), // → '.btn' 13 | }; 14 | visitEachChild(result.root, visitor); 15 | ``` 16 | 17 | ## Key Features 18 | 19 | - **Stylesheet Parsing**: Parse CSS/SCSS content using PostCSS with safe parsing 20 | - **AST Traversal**: Multiple traversal strategies for visiting CSS nodes 21 | - **Visitor Pattern**: Flexible visitor interface for processing different node types 22 | - **Source Location Mapping**: Convert AST nodes to linkable source locations 23 | - **Line Number Preservation**: Maintain accurate line numbers for error reporting 24 | - **Safe Parsing**: Graceful handling of malformed CSS using postcss-safe-parser 25 | 26 | ## Documentation map 27 | 28 | | Doc | What you'll find | 29 | | ------------------------------ | ------------------------------------------- | 30 | | [FUNCTIONS.md](./FUNCTIONS.md) | A–Z quick reference for every public symbol | 31 | | [EXAMPLES.md](./EXAMPLES.md) | Runnable scenarios with expected output | 32 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/validation/file-existence.ts: -------------------------------------------------------------------------------- ```typescript 1 | import * as fs from 'node:fs'; 2 | import * as path from 'node:path'; 3 | import { AngularMcpServerOptions } from './angular-mcp-server-options.schema.js'; 4 | 5 | export function validateAngularMcpServerFilesExist( 6 | config: AngularMcpServerOptions, 7 | ) { 8 | const root = config.workspaceRoot; 9 | 10 | if (!fs.existsSync(root)) { 11 | throw new Error(`workspaceRoot directory does not exist: ${root}`); 12 | } 13 | 14 | const missingFiles: string[] = []; 15 | 16 | // Always require uiRoot, optional: storybookDocsRoot, deprecatedCssClassesPath 17 | const dsPaths = [ 18 | config.ds.storybookDocsRoot 19 | ? { label: 'ds.storybookDocsRoot', relPath: config.ds.storybookDocsRoot } 20 | : null, 21 | config.ds.deprecatedCssClassesPath 22 | ? { 23 | label: 'ds.deprecatedCssClassesPath', 24 | relPath: config.ds.deprecatedCssClassesPath, 25 | } 26 | : null, 27 | { label: 'ds.uiRoot', relPath: config.ds.uiRoot }, 28 | ].filter(Boolean) as { label: string; relPath: string }[]; 29 | 30 | for (const { label, relPath } of dsPaths) { 31 | const absPath = path.resolve(root, relPath); 32 | if (!fs.existsSync(absPath)) { 33 | missingFiles.push(`${label} (resolved to: ${absPath})`); 34 | } 35 | } 36 | 37 | if (missingFiles.length > 0) { 38 | throw new Error( 39 | `The following required files or directories do not exist:\n` + 40 | missingFiles.join('\n'), 41 | ); 42 | } 43 | } 44 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/spec/meta.generator.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | import { generateMeta } from '../utils/meta.generator.js'; 4 | 5 | interface ParsedComponentStub { 6 | className?: string; 7 | selector?: string; 8 | } 9 | 10 | describe('generateMeta', () => { 11 | const templatePath = 'src/app/foo.component.html'; 12 | 13 | it('uses parsed component className and selector when provided', () => { 14 | const parsedComponent: ParsedComponentStub = { 15 | className: 'FooComponent', 16 | selector: 'app-foo', 17 | }; 18 | 19 | const meta = generateMeta(templatePath, parsedComponent as any, false); 20 | 21 | expect(meta.name).toBe('FooComponent'); 22 | expect(meta.selector).toBe('app-foo'); 23 | expect(meta.sourceFile).toBe(templatePath); 24 | expect(meta.templateType).toBe('external'); 25 | expect(meta.hash).toBe(''); 26 | expect(typeof meta.generatedAt).toBe('string'); 27 | }); 28 | 29 | it('falls back to filename when className or selector missing', () => { 30 | const parsedComponent: ParsedComponentStub = {}; 31 | 32 | const meta = generateMeta(templatePath, parsedComponent as any); 33 | 34 | expect(meta.name).toBe('foo.component'); 35 | expect(meta.selector).toBe('.foo.component'); 36 | }); 37 | 38 | it('sets templateType to inline when flag is true', () => { 39 | const parsedComponent: ParsedComponentStub = {}; 40 | 41 | const meta = generateMeta(templatePath, parsedComponent as any, true); 42 | 43 | expect(meta.templateType).toBe('inline'); 44 | }); 45 | }); 46 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/validation/angular-mcp-server-options.schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from 'zod'; 2 | import * as path from 'path'; 3 | 4 | const isAbsolutePath = (val: string) => path.isAbsolute(val); 5 | const isRelativePath = (val: string) => !path.isAbsolute(val); 6 | 7 | export const AngularMcpServerOptionsSchema = z.object({ 8 | workspaceRoot: z.string().refine(isAbsolutePath, { 9 | message: 10 | 'workspaceRoot must be an absolute path to the repository root where MCP server is working (e.g., /path/to/workspace-root)', 11 | }), 12 | ds: z.object({ 13 | storybookDocsRoot: z 14 | .string() 15 | .optional() 16 | .refine((val) => val === undefined || isRelativePath(val), { 17 | message: 18 | 'ds.storybookDocsRoot must be a relative path from workspace root to the storybook project root (e.g., path/to/storybook/components)', 19 | }), 20 | deprecatedCssClassesPath: z 21 | .string() 22 | .optional() 23 | .refine((val) => val === undefined || isRelativePath(val), { 24 | message: 25 | 'ds.deprecatedCssClassesPath must be a relative path from workspace root to the file component to deprecated css classes mapping (e.g., path/to/components-config.js)', 26 | }), 27 | uiRoot: z.string().refine(isRelativePath, { 28 | message: 29 | 'ds.uiRoot must be a relative path from workspace root to the components folder (e.g., path/to/components)', 30 | }), 31 | }), 32 | }); 33 | 34 | export type AngularMcpServerOptions = z.infer< 35 | typeof AngularMcpServerOptionsSchema 36 | >; 37 | ``` -------------------------------------------------------------------------------- /packages/shared/styles-ast-utils/src/lib/utils.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { Issue } from '@code-pushup/models'; 2 | import { Rule } from 'postcss'; 3 | 4 | /** 5 | * Convert a Root to an Issue source object and adjust its position based on startLine. 6 | * It creates a "linkable" source object for the issue. 7 | * By default, the source location is 0 indexed, so we add 1 to the startLine to make it work in file links. 8 | * 9 | * @param rule The AST rule to convert into a linkable source object. 10 | * @param startLine The positions of the asset contain the style rules. 11 | */ 12 | export function styleAstRuleToSource( 13 | { source }: Pick<Rule, 'source'>, 14 | startLine = 0, // 0 indexed 15 | ): Issue['source'] { 16 | if (source?.input.file == null) { 17 | throw new Error( 18 | 'style parsing was not initialized with a file path. Check the postcss options.', 19 | ); 20 | } 21 | const offset = startLine - 1; // -1 because PostCss is 1 indexed so we have to substract 1 to make is work in 0 based index 22 | 23 | return { 24 | file: source.input.file, 25 | position: { 26 | startLine: (source?.start?.line ?? 1) + offset + 1, // +1 because the code works 0 indexed and file links work 1 indexed. 27 | ...(source?.start?.column && { startColumn: source?.start?.column }), 28 | ...(source?.end?.line && { endLine: source?.end?.line + offset + 1 }), // +1 because the code works 0 indexed and file links work 1 indexed. 29 | ...(source?.end?.column && { endColumn: source?.end?.column }), 30 | }, 31 | }; 32 | } 33 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/shared/violation-analysis/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Shared types for violation analysis across file and folder reporting modules 3 | */ 4 | 5 | export interface BaseViolationOptions { 6 | cwd?: string; 7 | directory: string; 8 | componentName: string; 9 | deprecatedCssClassesPath?: string; 10 | } 11 | 12 | export interface BaseViolationIssue { 13 | message: string; 14 | source?: { 15 | file: string; 16 | position?: { 17 | startLine: number; 18 | }; 19 | }; 20 | } 21 | 22 | export interface BaseViolationAudit { 23 | details?: { 24 | issues?: BaseViolationIssue[]; 25 | }; 26 | title: string; 27 | score: number; 28 | } 29 | 30 | export interface BaseViolationResult { 31 | audits: BaseViolationAudit[]; 32 | [key: string]: unknown; 33 | } 34 | 35 | // Coverage analysis types (moved from project/utils/coverage-helpers.ts) 36 | export interface ReportCoverageParams { 37 | cwd?: string; 38 | returnRawData?: boolean; 39 | outputFormat?: 'text'; 40 | directory: string; 41 | dsComponents: DsComponent[]; 42 | } 43 | 44 | export interface DsComponent { 45 | componentName: string; 46 | deprecatedCssClasses: string[]; 47 | } 48 | 49 | export interface FormattedCoverageResult { 50 | textOutput: string; 51 | rawData?: { 52 | rawPluginResult: BaseViolationResult; 53 | pluginOptions: any; 54 | }; 55 | } 56 | 57 | // Shared file grouping types (consolidated from different modules) 58 | export interface FileGroup { 59 | message: string; 60 | lines: number[]; 61 | } 62 | 63 | export interface FileGroups { 64 | [fileName: string]: FileGroup; 65 | } 66 | 67 | // Performance-optimized path cache 68 | export interface PathCache { 69 | [key: string]: string; 70 | } 71 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/validation/ds-components.schema.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { z } from 'zod'; 2 | 3 | /** 4 | * Expected format example: 5 | * [ 6 | * { 7 | * componentName: 'DsButton', 8 | * deprecatedCssClasses: ['btn', 'btn-primary', 'legacy-button'] 9 | * }, 10 | * { 11 | * componentName: 'DsModal', 12 | * deprecatedCssClasses: ['modal'] 13 | * } 14 | * ] 15 | */ 16 | 17 | const EXAMPLE_FORMAT = `Expected: [{ componentName: 'DsButton', deprecatedCssClasses: ['btn'] }]`; 18 | 19 | export const DsComponentSchema = z.object({ 20 | componentName: z.string({ 21 | required_error: 22 | 'Missing required "componentName" field. Must be a string like "DsButton".', 23 | invalid_type_error: 24 | 'Invalid "componentName" type. Must be a string like "DsButton".', 25 | }), 26 | deprecatedCssClasses: z.array( 27 | z.string({ 28 | required_error: 29 | 'CSS class name must be a string. Example: "btn" or "legacy-button".', 30 | invalid_type_error: 'CSS class name must be a string.', 31 | }), 32 | { 33 | required_error: 34 | 'Missing required "deprecatedCssClasses" field. Must be an array like ["btn", "legacy-button"].', 35 | invalid_type_error: 36 | 'Invalid "deprecatedCssClasses" type. Must be an array of strings.', 37 | }, 38 | ), 39 | }); 40 | 41 | export const DsComponentsArraySchema = z.array(DsComponentSchema, { 42 | required_error: `Configuration must be an array of component objects. ${EXAMPLE_FORMAT}`, 43 | invalid_type_error: `Invalid configuration format. Must export an array. ${EXAMPLE_FORMAT}`, 44 | }); 45 | ``` -------------------------------------------------------------------------------- /packages/shared/ds-component-coverage/ai/API.md: -------------------------------------------------------------------------------- ```markdown 1 | # DS Component Coverage 2 | 3 | Small, zero‑dependency helpers for **measuring and asserting usage of Design System components** in Angular projects by detecting deprecated CSS classes. 4 | 5 | ## Minimal usage 6 | 7 | ```ts 8 | import { dsComponentCoveragePlugin } from '@push-based/ds-component-coverage'; 9 | 10 | const plugin = dsComponentCoveragePlugin({ 11 | directory: './src/app', 12 | dsComponents: [ 13 | { 14 | componentName: 'DsButton', 15 | deprecatedCssClasses: ['btn', 'button'], 16 | docsUrl: 'https://design-system.com/button', 17 | }, 18 | ], 19 | }); 20 | ``` 21 | 22 | ## Key Features 23 | 24 | - **CSS Class Detection**: Find deprecated CSS classes in templates and stylesheets 25 | - **Design System Migration**: Track migration progress from legacy styles to DS components 26 | - **Template Analysis**: Scan Angular templates for class usage in various binding types 27 | - **Style Analysis**: Detect deprecated class definitions in component styles 28 | - **Code Pushup Integration**: Built-in plugin for Code Pushup auditing framework 29 | - **Comprehensive Reporting**: Generate detailed reports with file locations and suggestions 30 | 31 | ## Documentation map 32 | 33 | | Doc | What you'll find | 34 | | ------------------------------ | ------------------------------------------- | 35 | | [FUNCTIONS.md](./FUNCTIONS.md) | A–Z quick reference for every public symbol | 36 | | [EXAMPLES.md](./EXAMPLES.md) | Runnable scenarios with expected output | 37 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/design-system/ui/modal/src/modal-header/modal-header.component.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { 2 | ChangeDetectionStrategy, 3 | Component, 4 | ViewEncapsulation, 5 | computed, 6 | inject, 7 | input, 8 | } from '@angular/core'; 9 | 10 | import { DsModalContext } from '../modal.component'; 11 | 12 | export const DS_MODAL_HEADER_VARIANT_ARRAY = [ 13 | 'surface-lowest', 14 | 'surface-low', 15 | 'surface', 16 | 'surface-high', 17 | 'nav-bg', 18 | ] as const; 19 | export type DsModalHeaderVariant = 20 | (typeof DS_MODAL_HEADER_VARIANT_ARRAY)[number]; 21 | 22 | @Component({ 23 | selector: 'ds-modal-header', 24 | standalone: true, 25 | template: ` 26 | <div class="ds-modal-header-container"> 27 | <div class="ds-modal-header-start"> 28 | <ng-content select="[slot=start]" /> 29 | </div> 30 | <div class="ds-modal-header-center"> 31 | <ng-content select="[slot=center]" /> 32 | <ng-content select="ds-modal-header-drag, [modal-header-image]" /> 33 | </div> 34 | <div class="ds-modal-header-end"> 35 | <ng-content select="[slot=end]" /> 36 | </div> 37 | </div> 38 | `, 39 | host: { 40 | class: 'ds-modal-header', 41 | '[class]': 'hostClass()', 42 | '[class.ds-modal-header-inverse]': 'context.inverse()', 43 | role: 'dialog', 44 | 'aria-label': 'Modal header dialog', 45 | }, 46 | changeDetection: ChangeDetectionStrategy.OnPush, 47 | encapsulation: ViewEncapsulation.None, 48 | }) 49 | export class DsModalHeader { 50 | variant = input<DsModalHeaderVariant>('surface'); 51 | protected context = inject(DsModalContext); 52 | 53 | protected hostClass = computed(() => `ds-modal-header-${this.variant()}`); 54 | } 55 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/complex-components/first-case/dashboard-demo.component.scss: -------------------------------------------------------------------------------- ```scss 1 | .demo-container { 2 | min-height: 100vh; 3 | background: #f9fafb; 4 | transition: background-color 0.2s ease; 5 | } 6 | 7 | .demo-container.dark { 8 | background: #111827; 9 | } 10 | 11 | .demo-content { 12 | padding: 2rem; 13 | max-width: 1200px; 14 | margin: 0 auto; 15 | } 16 | 17 | .demo-content h2 { 18 | color: #1f2937; 19 | margin-bottom: 1rem; 20 | } 21 | 22 | .demo-content p { 23 | color: #6b7280; 24 | margin-bottom: 2rem; 25 | } 26 | 27 | .demo-actions { 28 | display: flex; 29 | gap: 1rem; 30 | margin-bottom: 2rem; 31 | flex-wrap: wrap; 32 | } 33 | 34 | .demo-button { 35 | padding: 0.5rem 1rem; 36 | background: #3b82f6; 37 | color: white; 38 | border: none; 39 | border-radius: 0.375rem; 40 | cursor: pointer; 41 | font-size: 0.875rem; 42 | transition: background-color 0.2s ease; 43 | } 44 | 45 | .demo-button:hover { 46 | background: #2563eb; 47 | } 48 | 49 | .demo-log { 50 | background: white; 51 | border-radius: 0.5rem; 52 | padding: 1.5rem; 53 | box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); 54 | } 55 | 56 | .demo-log h3 { 57 | margin: 0 0 1rem 0; 58 | color: #1f2937; 59 | font-size: 1.125rem; 60 | } 61 | 62 | .log-entries { 63 | max-height: 300px; 64 | overflow-y: auto; 65 | } 66 | 67 | .log-entry { 68 | padding: 0.5rem; 69 | border-bottom: 1px solid #f3f4f6; 70 | font-size: 0.875rem; 71 | color: #374151; 72 | font-family: monospace; 73 | } 74 | 75 | .log-entry:last-child { 76 | border-bottom: none; 77 | } 78 | 79 | /* Dark mode styles */ 80 | .dark .demo-content h2 { 81 | color: #f9fafb; 82 | } 83 | 84 | .dark .demo-content p { 85 | color: #d1d5db; 86 | } 87 | 88 | .dark .demo-log { 89 | background: #374151; 90 | } 91 | 92 | .dark .demo-log h3 { 93 | color: #f9fafb; 94 | } 95 | 96 | .dark .log-entry { 97 | color: #d1d5db; 98 | border-bottom-color: #4b5563; 99 | } ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/styles.collector.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { readFileSync } from 'node:fs'; 2 | import { parseStylesheet, visitEachChild } from '@push-based/styles-ast-utils'; 3 | import { selectorMatches } from './css-match.js'; 4 | import type { 5 | StyleDeclarations, 6 | DomStructure, 7 | } from '../../shared/models/types.js'; 8 | import type { Declaration, Rule } from 'postcss'; 9 | 10 | /** 11 | * Collect styles with explicit DOM relationships 12 | */ 13 | export async function collectStylesV2( 14 | scssPath: string, 15 | dom: DomStructure, 16 | ): Promise<StyleDeclarations> { 17 | const styles: StyleDeclarations = { sourceFile: scssPath, rules: {} }; 18 | const scssContent = readFileSync(scssPath, 'utf-8'); 19 | const parsedStyles = parseStylesheet(scssContent, scssPath); 20 | 21 | if (parsedStyles.root.type === 'root') { 22 | visitEachChild(parsedStyles.root, { 23 | visitRule: (rule: Rule) => { 24 | const properties: Record<string, string> = {}; 25 | 26 | rule.walkDecls?.((decl: Declaration) => { 27 | properties[decl.prop] = decl.value; 28 | }); 29 | 30 | styles.rules[rule.selector] = { 31 | appliesTo: findMatchingDomElements(rule.selector, dom), 32 | properties, 33 | }; 34 | }, 35 | }); 36 | } 37 | 38 | return styles; 39 | } 40 | 41 | /** 42 | * Find DOM elements that match a CSS selector 43 | */ 44 | function findMatchingDomElements( 45 | cssSelector: string, 46 | dom: DomStructure, 47 | ): string[] { 48 | return Object.entries(dom) 49 | .filter(([domKey, element]) => 50 | selectorMatches(cssSelector, domKey, element), 51 | ) 52 | .map(([domKey]) => domKey); 53 | } 54 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/components/refactoring-tests/complex-components/first-case/dashboard-demo.component.html: -------------------------------------------------------------------------------- ```html 1 | <div class="demo-container"> 2 | <app-dashboard-header 3 | [badgeType]="'premium'" 4 | [badgeSize]="'medium'" 5 | [animated]="true" 6 | [pulsing]="false" 7 | [dismissible]="true" 8 | [userProfile]="userProfile()" 9 | [darkMode]="darkMode()" 10 | (searchPerformed)="onSearchPerformed($event)" 11 | (badgeDismissed)="onBadgeDismissed()" 12 | (notificationClicked)="onNotificationClicked($event)" 13 | (userActionClicked)="onUserActionClicked($event)" 14 | (themeToggled)="onThemeToggled($event)"> 15 | 16 | <!-- Custom icon for the offer badge --> 17 | <span slot="start">⭐</span> 18 | 19 | <!-- Custom badge content --> 20 | Premium Features Available! 21 | </app-dashboard-header> 22 | 23 | <div class="demo-content"> 24 | <h2>Dashboard Content</h2> 25 | <p>This is a demo of the complex dashboard header component.</p> 26 | 27 | <div class="demo-actions"> 28 | <button (click)="toggleTheme()" class="demo-button"> 29 | Toggle Theme (Current: {{ darkMode() ? 'Dark' : 'Light' }}) 30 | </button> 31 | 32 | <button (click)="changeUser()" class="demo-button"> 33 | Change User 34 | </button> 35 | 36 | <button (click)="addNotification()" class="demo-button"> 37 | Add Notification 38 | </button> 39 | </div> 40 | 41 | <div class="demo-log"> 42 | <h3>Event Log:</h3> 43 | <div class="log-entries"> 44 | @for (entry of eventLog(); track $index) { 45 | <div class="log-entry">{{ entry }}</div> 46 | } 47 | </div> 48 | </div> 49 | </div> 50 | </div> ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-contract/builder/utils/element-helpers.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { 2 | TmplAstElement, 3 | ASTWithSource, 4 | } from '@angular/compiler' with { 'resolution-mode': 'import' }; 5 | 6 | /** 7 | * Extract bindings from a template element 8 | */ 9 | export function extractBindings(element: TmplAstElement) { 10 | return element.inputs.map((input) => ({ 11 | type: getBindingType(input.name), 12 | name: input.name, 13 | source: (input.value as ASTWithSource).source || '', 14 | sourceSpan: input.sourceSpan 15 | ? { 16 | start: input.sourceSpan.start.offset, 17 | end: input.sourceSpan.end.offset, 18 | file: input.sourceSpan.start.file.url, 19 | } 20 | : undefined, 21 | })); 22 | } 23 | 24 | /** 25 | * Extract attributes from a template element 26 | */ 27 | export function extractAttributes(element: TmplAstElement) { 28 | return element.attributes.map((attr) => ({ 29 | type: 'attribute' as const, 30 | name: attr.name, 31 | source: attr.value, 32 | })); 33 | } 34 | 35 | /** 36 | * Extract events from a template element 37 | */ 38 | export function extractEvents(element: TmplAstElement) { 39 | return element.outputs.map((output) => ({ 40 | name: output.name, 41 | handler: (output.handler as ASTWithSource).source || '', 42 | })); 43 | } 44 | 45 | /** 46 | * Determine the binding type based on the binding name 47 | */ 48 | function getBindingType( 49 | bindingName: string, 50 | ): 'class' | 'style' | 'property' | 'attribute' { 51 | if (bindingName.startsWith('class.')) return 'class'; 52 | if (bindingName.startsWith('style.')) return 'style'; 53 | if (bindingName.startsWith('attr.')) return 'attribute'; 54 | return 'property'; 55 | } 56 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component/get-deprecated-css-classes.tool.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { ToolSchemaOptions } from '@push-based/models'; 2 | import { createHandler } from '../shared/utils/handler-helpers.js'; 3 | import { 4 | createComponentInputSchema, 5 | COMMON_ANNOTATIONS, 6 | } from '../shared/models/schema-helpers.js'; 7 | import { getDeprecatedCssClasses } from './utils/deprecated-css-helpers.js'; 8 | 9 | interface DeprecatedCssClassesOptions { 10 | componentName: string; 11 | } 12 | 13 | export const getDeprecatedCssClassesSchema: ToolSchemaOptions = { 14 | name: 'get-deprecated-css-classes', 15 | description: `List deprecated CSS classes for a DS component.`, 16 | inputSchema: createComponentInputSchema( 17 | 'The class name of the component to get deprecated classes for (e.g., DsButton)', 18 | ), 19 | annotations: { 20 | title: 'Get Deprecated CSS Classes', 21 | ...COMMON_ANNOTATIONS.readOnly, 22 | }, 23 | }; 24 | 25 | export const getDeprecatedCssClassesHandler = createHandler< 26 | DeprecatedCssClassesOptions, 27 | string[] 28 | >( 29 | getDeprecatedCssClassesSchema.name, 30 | async ({ componentName }, { cwd, deprecatedCssClassesPath }) => { 31 | if (!deprecatedCssClassesPath) { 32 | throw new Error( 33 | 'Missing ds.deprecatedCssClassesPath. Provide --ds.deprecatedCssClassesPath in mcp.json file.', 34 | ); 35 | } 36 | return await getDeprecatedCssClasses( 37 | componentName, 38 | deprecatedCssClassesPath, 39 | cwd, 40 | ); 41 | }, 42 | (result) => result, 43 | ); 44 | 45 | export const getDeprecatedCssClassesTools = [ 46 | { 47 | schema: getDeprecatedCssClassesSchema, 48 | handler: getDeprecatedCssClassesHandler, 49 | }, 50 | ]; 51 | ``` -------------------------------------------------------------------------------- /packages/shared/styles-ast-utils/src/lib/stylesheet.parse.unit.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect } from 'vitest'; 2 | import { parseStylesheet } from './stylesheet.parse.js'; 3 | import { Rule } from 'postcss'; 4 | 5 | describe('parseStylesheet', () => { 6 | it('should have line numbers starting from 1', () => { 7 | const result = parseStylesheet(`.btn{ color: red; }`, 'styles.css').root; 8 | const { source = {} as Exclude<Rule['source'], undefined> } = 9 | result.nodes.at(0) as Rule; 10 | const { start, end } = source; 11 | 12 | expect(start?.line).toBe(1); 13 | expect(start?.column).toBe(1); 14 | expect(end?.line).toBe(1); 15 | expect(end?.column).toBe(19); 16 | }); 17 | 18 | it('should have correct line number for starting line breaks', () => { 19 | const result = parseStylesheet( 20 | ` 21 | 22 | .btn{ color: red; }`, 23 | 'styles.css', 24 | ).root; 25 | 26 | const { source = {} as Exclude<Rule['source'], undefined> } = 27 | result?.nodes?.at(0) as Rule; 28 | const { start, end } = source; 29 | 30 | expect(start?.line).toBe(3); 31 | expect(start?.column).toBe(1); 32 | expect(end?.line).toBe(3); 33 | expect(end?.column).toBe(19); 34 | }); 35 | 36 | it('should have correct line number for spans', () => { 37 | const result = parseStylesheet( 38 | ` 39 | .btn{ 40 | color: red; 41 | }`, 42 | 'styles.css', 43 | ).root; 44 | 45 | const { source = {} as Exclude<Rule['source'], undefined> } = 46 | result?.nodes?.at(0) as Rule; 47 | const { start, end } = source; 48 | 49 | expect(start?.line).toBe(2); 50 | expect(start?.column).toBe(1); 51 | expect(end?.line).toBe(4); 52 | expect(end?.column).toBe(1); 53 | }); 54 | }); 55 | ``` -------------------------------------------------------------------------------- /packages/shared/ds-component-coverage/src/lib/runner/audits/ds-coverage/ds-coverage.audit.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { getCompCoverageAuditOutput } from './utils.js'; 2 | import { AuditOutputs, Issue } from '@code-pushup/models'; 3 | import { 4 | ParsedComponent, 5 | visitComponentStyles, 6 | visitComponentTemplate, 7 | Asset, 8 | } from '@push-based/angular-ast-utils'; 9 | import type { ParsedTemplate } from '@angular/compiler' with { 'resolution-mode': 'import' }; 10 | import { ComponentReplacement } from './schema.js'; 11 | import { getClassUsageIssues } from './class-usage.utils.js'; 12 | import { getClassDefinitionIssues } from './class-definition.utils.js'; 13 | 14 | export function dsCompCoverageAuditOutputs( 15 | dsComponents: ComponentReplacement[], 16 | parsedComponents: ParsedComponent[], 17 | ): Promise<AuditOutputs> { 18 | return Promise.all( 19 | dsComponents.map(async (dsComponent) => { 20 | const allIssues = ( 21 | await Promise.all( 22 | parsedComponents.flatMap(async (component) => { 23 | return [ 24 | ...(await visitComponentTemplate( 25 | component, 26 | dsComponent, 27 | getClassUsageIssues as ( 28 | tokenReplacement: ComponentReplacement, 29 | asset: Asset<ParsedTemplate>, 30 | ) => Promise<Issue[]>, 31 | )), 32 | ...(await visitComponentStyles( 33 | component, 34 | dsComponent, 35 | getClassDefinitionIssues, 36 | )), 37 | ]; 38 | }), 39 | ) 40 | ).flat(); 41 | 42 | return getCompCoverageAuditOutput(dsComponent, allIssues); 43 | }), 44 | ); 45 | } 46 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/styles/new-styles-4.scss: -------------------------------------------------------------------------------- ```scss 1 | // Example SCSS file with deprecated and non-deprecated classes 2 | .example-class-7 { 3 | text-align: center; 4 | } 5 | 6 | .modal { 7 | width: 100%; 8 | } 9 | 10 | .example-class-8 { 11 | height: 50px; 12 | } 13 | 14 | .card { 15 | box-shadow: 0 0 10px gray; 16 | } 17 | 18 | .example-class-21 { 19 | color: red; 20 | } 21 | 22 | .example-class-22 { 23 | background-color: green; 24 | } 25 | 26 | .example-class-23 { 27 | font-size: 18px; 28 | } 29 | 30 | .example-class-24 { 31 | padding: 10px; 32 | } 33 | 34 | .example-class-25 { 35 | margin: 5px; 36 | } 37 | 38 | .example-class-26 { 39 | border: 1px solid black; 40 | } 41 | 42 | .example-class-27 { 43 | text-align: left; 44 | } 45 | 46 | .example-class-28 { 47 | line-height: 2; 48 | } 49 | 50 | .example-class-29 { 51 | font-weight: normal; 52 | } 53 | 54 | .example-class-30 { 55 | display: inline-block; 56 | } 57 | 58 | .example-class-31 { 59 | width: 50%; 60 | } 61 | 62 | .example-class-32 { 63 | height: 100px; 64 | } 65 | 66 | .example-class-33 { 67 | overflow: auto; 68 | } 69 | 70 | .example-class-34 { 71 | position: relative; 72 | } 73 | 74 | .example-class-35 { 75 | top: 10px; 76 | } 77 | 78 | .example-class-36 { 79 | left: 20px; 80 | } 81 | 82 | .example-class-37 { 83 | right: 30px; 84 | } 85 | 86 | .example-class-38 { 87 | bottom: 40px; 88 | } 89 | 90 | .example-class-39 { 91 | z-index: 1; 92 | } 93 | 94 | .example-class-40 { 95 | opacity: 0.5; 96 | } 97 | 98 | .example-class-41 { 99 | visibility: hidden; 100 | } 101 | 102 | .example-class-42 { 103 | cursor: pointer; 104 | } 105 | 106 | .example-class-43 { 107 | transition: all 0.3s ease; 108 | } 109 | 110 | .example-class-44 { 111 | transform: rotate(45deg); 112 | } 113 | 114 | .example-class-45 { 115 | animation: fadeIn 1s; 116 | } 117 | 118 | .example-class-46 { 119 | box-sizing: border-box; 120 | } 121 | 122 | .example-class-47 { 123 | content: ''; 124 | } 125 | 126 | .example-class-48 { 127 | clip: rect(0, 0, 0, 0); 128 | } 129 | 130 | .example-class-49 { 131 | float: left; 132 | } 133 | 134 | .example-class-50 { 135 | clear: both; 136 | } 137 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/shared/utils/component-validation.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { COMPONENT_REGEXES } from './regex-helpers.js'; 2 | 3 | /** 4 | * Validates that a component name is a valid Design System component name 5 | * Accepts both formats: "Button" and "DsButton" 6 | * @param componentName The component name to validate 7 | * @throws Error if the component name is invalid 8 | */ 9 | export function validateComponentName( 10 | componentName: unknown, 11 | ): asserts componentName is string { 12 | if ( 13 | !componentName || 14 | typeof componentName !== 'string' || 15 | !COMPONENT_REGEXES.isValidDsComponent(componentName) 16 | ) { 17 | throw new Error( 18 | 'Invalid component name. Must be a valid PascalCase string (e.g., "Button" or "DsButton").', 19 | ); 20 | } 21 | } 22 | 23 | /** 24 | * Converts a Design System component name to kebab case 25 | * @param componentName The component name (e.g., "DsButton" or "Button") 26 | * @returns The kebab case name (e.g., "button") 27 | */ 28 | export function componentNameToKebabCase(componentName: string): string { 29 | const kebabCase = COMPONENT_REGEXES.toKebabCase(componentName); 30 | 31 | if (!kebabCase?.trim()?.length) { 32 | throw new Error( 33 | 'Invalid component name. Must be a valid PascalCase string (e.g., "Button" or "DsButton").', 34 | ); 35 | } 36 | 37 | return kebabCase; 38 | } 39 | 40 | /** 41 | * Creates a tag name from a component name 42 | * @param componentName The component name (e.g., "DsButton" or "Button") 43 | * @returns The tag name (e.g., "ds-button") 44 | */ 45 | export function componentNameToTagName(componentName: string): string { 46 | return `ds-${componentNameToKebabCase(componentName)}`; 47 | } 48 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/styles/new-styles-1.scss: -------------------------------------------------------------------------------- ```scss 1 | // Example SCSS file with deprecated and non-deprecated classes 2 | .example-class-1 { 3 | color: blue; 4 | } 5 | 6 | .pill-with-badge { 7 | background-color: red; 8 | } 9 | 10 | .example-class-2 { 11 | margin: 10px; 12 | } 13 | 14 | .btn-primary { 15 | padding: 5px; 16 | } 17 | 18 | .example-class-21 { 19 | color: red; 20 | } 21 | 22 | .example-class-22 { 23 | background-color: green; 24 | } 25 | 26 | .example-class-23 { 27 | font-size: 18px; 28 | } 29 | 30 | .example-class-24 { 31 | padding: 10px; 32 | } 33 | 34 | .example-class-25 { 35 | margin: 5px; 36 | } 37 | 38 | .example-class-26 { 39 | border: 1px solid black; 40 | } 41 | 42 | .example-class-27 { 43 | text-align: left; 44 | } 45 | 46 | .example-class-28 { 47 | line-height: 2; 48 | } 49 | 50 | .example-class-29 { 51 | font-weight: normal; 52 | } 53 | 54 | .example-class-30 { 55 | display: inline-block; 56 | } 57 | 58 | .example-class-31 { 59 | width: 50%; 60 | } 61 | 62 | .example-class-32 { 63 | height: 100px; 64 | } 65 | 66 | .example-class-33 { 67 | overflow: auto; 68 | } 69 | 70 | .example-class-34 { 71 | position: relative; 72 | } 73 | 74 | .example-class-35 { 75 | top: 10px; 76 | } 77 | 78 | .example-class-36 { 79 | left: 20px; 80 | } 81 | 82 | .example-class-37 { 83 | right: 30px; 84 | } 85 | 86 | .example-class-38 { 87 | bottom: 40px; 88 | } 89 | 90 | .example-class-39 { 91 | z-index: 1; 92 | } 93 | 94 | .example-class-40 { 95 | opacity: 0.5; 96 | } 97 | 98 | .example-class-41 { 99 | visibility: hidden; 100 | } 101 | 102 | .example-class-42 { 103 | cursor: pointer; 104 | } 105 | 106 | .example-class-43 { 107 | transition: all 0.3s ease; 108 | } 109 | 110 | .example-class-44 { 111 | transform: rotate(45deg); 112 | } 113 | 114 | .example-class-45 { 115 | animation: fadeIn 1s; 116 | } 117 | 118 | .example-class-46 { 119 | box-sizing: border-box; 120 | } 121 | 122 | .example-class-47 { 123 | content: ''; 124 | } 125 | 126 | .example-class-48 { 127 | clip: rect(0, 0, 0, 0); 128 | } 129 | 130 | .example-class-49 { 131 | float: left; 132 | } 133 | 134 | .example-class-50 { 135 | clear: both; 136 | } 137 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/styles/new-styles-2.scss: -------------------------------------------------------------------------------- ```scss 1 | // Example SCSS file with deprecated and non-deprecated classes 2 | .example-class-3 { 3 | font-size: 14px; 4 | } 5 | 6 | .offer-badge { 7 | border: 1px solid black; 8 | } 9 | 10 | .example-class-4 { 11 | padding: 20px; 12 | } 13 | 14 | .nav-tabs { 15 | display: flex; 16 | } 17 | 18 | .example-class-21 { 19 | color: red; 20 | } 21 | 22 | .example-class-22 { 23 | background-color: green; 24 | } 25 | 26 | .example-class-23 { 27 | font-size: 18px; 28 | } 29 | 30 | .example-class-24 { 31 | padding: 10px; 32 | } 33 | 34 | .example-class-25 { 35 | margin: 5px; 36 | } 37 | 38 | .example-class-26 { 39 | border: 1px solid black; 40 | } 41 | 42 | .example-class-27 { 43 | text-align: left; 44 | } 45 | 46 | .example-class-28 { 47 | line-height: 2; 48 | } 49 | 50 | .example-class-29 { 51 | font-weight: normal; 52 | } 53 | 54 | .example-class-30 { 55 | display: inline-block; 56 | } 57 | 58 | .example-class-31 { 59 | width: 50%; 60 | } 61 | 62 | .example-class-32 { 63 | height: 100px; 64 | } 65 | 66 | .example-class-33 { 67 | overflow: auto; 68 | } 69 | 70 | .example-class-34 { 71 | position: relative; 72 | } 73 | 74 | .example-class-35 { 75 | top: 10px; 76 | } 77 | 78 | .example-class-36 { 79 | left: 20px; 80 | } 81 | 82 | .example-class-37 { 83 | right: 30px; 84 | } 85 | 86 | .example-class-38 { 87 | bottom: 40px; 88 | } 89 | 90 | .example-class-39 { 91 | z-index: 1; 92 | } 93 | 94 | .example-class-40 { 95 | opacity: 0.5; 96 | } 97 | 98 | .example-class-41 { 99 | visibility: hidden; 100 | } 101 | 102 | .example-class-42 { 103 | cursor: pointer; 104 | } 105 | 106 | .example-class-43 { 107 | transition: all 0.3s ease; 108 | } 109 | 110 | .example-class-44 { 111 | transform: rotate(45deg); 112 | } 113 | 114 | .example-class-45 { 115 | animation: fadeIn 1s; 116 | } 117 | 118 | .example-class-46 { 119 | box-sizing: border-box; 120 | } 121 | 122 | .example-class-47 { 123 | content: ''; 124 | } 125 | 126 | .example-class-48 { 127 | clip: rect(0, 0, 0, 0); 128 | } 129 | 130 | .example-class-49 { 131 | float: left; 132 | } 133 | 134 | .example-class-50 { 135 | clear: both; 136 | } 137 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/styles/new-styles-3.scss: -------------------------------------------------------------------------------- ```scss 1 | // Example SCSS file with deprecated and non-deprecated classes 2 | .example-class-5 { 3 | background-color: yellow; 4 | } 5 | 6 | .tab-nav-item { 7 | color: green; 8 | } 9 | 10 | .example-class-6 { 11 | border-radius: 5px; 12 | } 13 | 14 | .legacy-button { 15 | font-weight: bold; 16 | } 17 | 18 | .example-class-21 { 19 | color: red; 20 | } 21 | 22 | .example-class-22 { 23 | background-color: green; 24 | } 25 | 26 | .example-class-23 { 27 | font-size: 18px; 28 | } 29 | 30 | .example-class-24 { 31 | padding: 10px; 32 | } 33 | 34 | .example-class-25 { 35 | margin: 5px; 36 | } 37 | 38 | .example-class-26 { 39 | border: 1px solid black; 40 | } 41 | 42 | .example-class-27 { 43 | text-align: left; 44 | } 45 | 46 | .example-class-28 { 47 | line-height: 2; 48 | } 49 | 50 | .example-class-29 { 51 | font-weight: normal; 52 | } 53 | 54 | .example-class-30 { 55 | display: inline-block; 56 | } 57 | 58 | .example-class-31 { 59 | width: 50%; 60 | } 61 | 62 | .example-class-32 { 63 | height: 100px; 64 | } 65 | 66 | .example-class-33 { 67 | overflow: auto; 68 | } 69 | 70 | .example-class-34 { 71 | position: relative; 72 | } 73 | 74 | .example-class-35 { 75 | top: 10px; 76 | } 77 | 78 | .example-class-36 { 79 | left: 20px; 80 | } 81 | 82 | .example-class-37 { 83 | right: 30px; 84 | } 85 | 86 | .example-class-38 { 87 | bottom: 40px; 88 | } 89 | 90 | .example-class-39 { 91 | z-index: 1; 92 | } 93 | 94 | .example-class-40 { 95 | opacity: 0.5; 96 | } 97 | 98 | .example-class-41 { 99 | visibility: hidden; 100 | } 101 | 102 | .example-class-42 { 103 | cursor: pointer; 104 | } 105 | 106 | .example-class-43 { 107 | transition: all 0.3s ease; 108 | } 109 | 110 | .example-class-44 { 111 | transform: rotate(45deg); 112 | } 113 | 114 | .example-class-45 { 115 | animation: fadeIn 1s; 116 | } 117 | 118 | .example-class-46 { 119 | box-sizing: border-box; 120 | } 121 | 122 | .example-class-47 { 123 | content: ''; 124 | } 125 | 126 | .example-class-48 { 127 | clip: rect(0, 0, 0, 0); 128 | } 129 | 130 | .example-class-49 { 131 | float: left; 132 | } 133 | 134 | .example-class-50 { 135 | clear: both; 136 | } 137 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/styles/new-styles-6.scss: -------------------------------------------------------------------------------- ```scss 1 | // Example SCSS file with deprecated and non-deprecated classes 2 | .example-class-11 { 3 | font-family: Arial, sans-serif; 4 | } 5 | 6 | .divider { 7 | border-top: 1px solid #ccc; 8 | } 9 | 10 | .example-class-12 { 11 | padding-left: 15px; 12 | } 13 | 14 | .count { 15 | font-size: 12px; 16 | } 17 | 18 | .example-class-21 { 19 | color: red; 20 | } 21 | 22 | .example-class-22 { 23 | background-color: green; 24 | } 25 | 26 | .example-class-23 { 27 | font-size: 18px; 28 | } 29 | 30 | .example-class-24 { 31 | padding: 10px; 32 | } 33 | 34 | .example-class-25 { 35 | margin: 5px; 36 | } 37 | 38 | .example-class-26 { 39 | border: 1px solid black; 40 | } 41 | 42 | .example-class-27 { 43 | text-align: left; 44 | } 45 | 46 | .example-class-28 { 47 | line-height: 2; 48 | } 49 | 50 | .example-class-29 { 51 | font-weight: normal; 52 | } 53 | 54 | .example-class-30 { 55 | display: inline-block; 56 | } 57 | 58 | .example-class-31 { 59 | width: 50%; 60 | } 61 | 62 | .example-class-32 { 63 | height: 100px; 64 | } 65 | 66 | .example-class-33 { 67 | overflow: auto; 68 | } 69 | 70 | .example-class-34 { 71 | position: relative; 72 | } 73 | 74 | .example-class-35 { 75 | top: 10px; 76 | } 77 | 78 | .example-class-36 { 79 | left: 20px; 80 | } 81 | 82 | .example-class-37 { 83 | right: 30px; 84 | } 85 | 86 | .example-class-38 { 87 | bottom: 40px; 88 | } 89 | 90 | .example-class-39 { 91 | z-index: 1; 92 | } 93 | 94 | .example-class-40 { 95 | opacity: 0.5; 96 | } 97 | 98 | .example-class-41 { 99 | visibility: hidden; 100 | } 101 | 102 | .example-class-42 { 103 | cursor: pointer; 104 | } 105 | 106 | .example-class-43 { 107 | transition: all 0.3s ease; 108 | } 109 | 110 | .example-class-44 { 111 | transform: rotate(45deg); 112 | } 113 | 114 | .example-class-45 { 115 | animation: fadeIn 1s; 116 | } 117 | 118 | .example-class-46 { 119 | box-sizing: border-box; 120 | } 121 | 122 | .example-class-47 { 123 | content: ''; 124 | } 125 | 126 | .example-class-48 { 127 | clip: rect(0, 0, 0, 0); 128 | } 129 | 130 | .example-class-49 { 131 | float: left; 132 | } 133 | 134 | .example-class-50 { 135 | clear: both; 136 | } 137 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/styles/new-styles-7.scss: -------------------------------------------------------------------------------- ```scss 1 | // Example SCSS file with deprecated and non-deprecated classes 2 | .example-class-13 { 3 | margin-right: 10px; 4 | } 5 | 6 | .badge-circle { 7 | border-radius: 50%; 8 | } 9 | 10 | .example-class-14 { 11 | color: #333; 12 | } 13 | 14 | .custom-control-checkbox { 15 | display: inline-block; 16 | } 17 | 18 | .example-class-21 { 19 | color: red; 20 | } 21 | 22 | .example-class-22 { 23 | background-color: green; 24 | } 25 | 26 | .example-class-23 { 27 | font-size: 18px; 28 | } 29 | 30 | .example-class-24 { 31 | padding: 10px; 32 | } 33 | 34 | .example-class-25 { 35 | margin: 5px; 36 | } 37 | 38 | .example-class-26 { 39 | border: 1px solid black; 40 | } 41 | 42 | .example-class-27 { 43 | text-align: left; 44 | } 45 | 46 | .example-class-28 { 47 | line-height: 2; 48 | } 49 | 50 | .example-class-29 { 51 | font-weight: normal; 52 | } 53 | 54 | .example-class-30 { 55 | display: inline-block; 56 | } 57 | 58 | .example-class-31 { 59 | width: 50%; 60 | } 61 | 62 | .example-class-32 { 63 | height: 100px; 64 | } 65 | 66 | .example-class-33 { 67 | overflow: auto; 68 | } 69 | 70 | .example-class-34 { 71 | position: relative; 72 | } 73 | 74 | .example-class-35 { 75 | top: 10px; 76 | } 77 | 78 | .example-class-36 { 79 | left: 20px; 80 | } 81 | 82 | .example-class-37 { 83 | right: 30px; 84 | } 85 | 86 | .example-class-38 { 87 | bottom: 40px; 88 | } 89 | 90 | .example-class-39 { 91 | z-index: 1; 92 | } 93 | 94 | .example-class-40 { 95 | opacity: 0.5; 96 | } 97 | 98 | .example-class-41 { 99 | visibility: hidden; 100 | } 101 | 102 | .example-class-42 { 103 | cursor: pointer; 104 | } 105 | 106 | .example-class-43 { 107 | transition: all 0.3s ease; 108 | } 109 | 110 | .example-class-44 { 111 | transform: rotate(45deg); 112 | } 113 | 114 | .example-class-45 { 115 | animation: fadeIn 1s; 116 | } 117 | 118 | .example-class-46 { 119 | box-sizing: border-box; 120 | } 121 | 122 | .example-class-47 { 123 | content: ''; 124 | } 125 | 126 | .example-class-48 { 127 | clip: rect(0, 0, 0, 0); 128 | } 129 | 130 | .example-class-49 { 131 | float: left; 132 | } 133 | 134 | .example-class-50 { 135 | clear: both; 136 | } 137 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/styles/new-styles-5.scss: -------------------------------------------------------------------------------- ```scss 1 | // Example SCSS file with deprecated and non-deprecated classes 2 | .example-class-9 { 3 | line-height: 1.5; 4 | } 5 | 6 | .loading { 7 | animation: spin 2s linear infinite; 8 | } 9 | 10 | .example-class-10 { 11 | max-width: 200px; 12 | } 13 | 14 | .collapsible-container { 15 | overflow: hidden; 16 | } 17 | 18 | .example-class-21 { 19 | color: red; 20 | } 21 | 22 | .example-class-22 { 23 | background-color: green; 24 | } 25 | 26 | .example-class-23 { 27 | font-size: 18px; 28 | } 29 | 30 | .example-class-24 { 31 | padding: 10px; 32 | } 33 | 34 | .example-class-25 { 35 | margin: 5px; 36 | } 37 | 38 | .example-class-26 { 39 | border: 1px solid black; 40 | } 41 | 42 | .example-class-27 { 43 | text-align: left; 44 | } 45 | 46 | .example-class-28 { 47 | line-height: 2; 48 | } 49 | 50 | .example-class-29 { 51 | font-weight: normal; 52 | } 53 | 54 | .example-class-30 { 55 | display: inline-block; 56 | } 57 | 58 | .example-class-31 { 59 | width: 50%; 60 | } 61 | 62 | .example-class-32 { 63 | height: 100px; 64 | } 65 | 66 | .example-class-33 { 67 | overflow: auto; 68 | } 69 | 70 | .example-class-34 { 71 | position: relative; 72 | } 73 | 74 | .example-class-35 { 75 | top: 10px; 76 | } 77 | 78 | .example-class-36 { 79 | left: 20px; 80 | } 81 | 82 | .example-class-37 { 83 | right: 30px; 84 | } 85 | 86 | .example-class-38 { 87 | bottom: 40px; 88 | } 89 | 90 | .example-class-39 { 91 | z-index: 1; 92 | } 93 | 94 | .example-class-40 { 95 | opacity: 0.5; 96 | } 97 | 98 | .example-class-41 { 99 | visibility: hidden; 100 | } 101 | 102 | .example-class-42 { 103 | cursor: pointer; 104 | } 105 | 106 | .example-class-43 { 107 | transition: all 0.3s ease; 108 | } 109 | 110 | .example-class-44 { 111 | transform: rotate(45deg); 112 | } 113 | 114 | .example-class-45 { 115 | animation: fadeIn 1s; 116 | } 117 | 118 | .example-class-46 { 119 | box-sizing: border-box; 120 | } 121 | 122 | .example-class-47 { 123 | content: ''; 124 | } 125 | 126 | .example-class-48 { 127 | clip: rect(0, 0, 0, 0); 128 | } 129 | 130 | .example-class-49 { 131 | float: left; 132 | } 133 | 134 | .example-class-50 { 135 | clear: both; 136 | } 137 | ``` -------------------------------------------------------------------------------- /packages/minimal-repo/packages/application/src/app/styles/new-styles-8.scss: -------------------------------------------------------------------------------- ```scss 1 | // Example SCSS file with deprecated and non-deprecated classes 2 | .example-class-15 { 3 | background: #f0f0f0; 4 | } 5 | 6 | .custom-control-radio { 7 | margin-bottom: 5px; 8 | } 9 | 10 | .example-class-16 { 11 | border: 2px solid #000; 12 | } 13 | 14 | .form-control-tabs-segmented-v2 { 15 | display: block; 16 | } 17 | 18 | .example-class-21 { 19 | color: red; 20 | } 21 | 22 | .example-class-22 { 23 | background-color: green; 24 | } 25 | 26 | .example-class-23 { 27 | font-size: 18px; 28 | } 29 | 30 | .example-class-24 { 31 | padding: 10px; 32 | } 33 | 34 | .example-class-25 { 35 | margin: 5px; 36 | } 37 | 38 | .example-class-26 { 39 | border: 1px solid black; 40 | } 41 | 42 | .example-class-27 { 43 | text-align: left; 44 | } 45 | 46 | .example-class-28 { 47 | line-height: 2; 48 | } 49 | 50 | .example-class-29 { 51 | font-weight: normal; 52 | } 53 | 54 | .example-class-30 { 55 | display: inline-block; 56 | } 57 | 58 | .example-class-31 { 59 | width: 50%; 60 | } 61 | 62 | .example-class-32 { 63 | height: 100px; 64 | } 65 | 66 | .example-class-33 { 67 | overflow: auto; 68 | } 69 | 70 | .example-class-34 { 71 | position: relative; 72 | } 73 | 74 | .example-class-35 { 75 | top: 10px; 76 | } 77 | 78 | .example-class-36 { 79 | left: 20px; 80 | } 81 | 82 | .example-class-37 { 83 | right: 30px; 84 | } 85 | 86 | .example-class-38 { 87 | bottom: 40px; 88 | } 89 | 90 | .example-class-39 { 91 | z-index: 1; 92 | } 93 | 94 | .example-class-40 { 95 | opacity: 0.5; 96 | } 97 | 98 | .example-class-41 { 99 | visibility: hidden; 100 | } 101 | 102 | .example-class-42 { 103 | cursor: pointer; 104 | } 105 | 106 | .example-class-43 { 107 | transition: all 0.3s ease; 108 | } 109 | 110 | .example-class-44 { 111 | transform: rotate(45deg); 112 | } 113 | 114 | .example-class-45 { 115 | animation: fadeIn 1s; 116 | } 117 | 118 | .example-class-46 { 119 | box-sizing: border-box; 120 | } 121 | 122 | .example-class-47 { 123 | content: ''; 124 | } 125 | 126 | .example-class-48 { 127 | clip: rect(0, 0, 0, 0); 128 | } 129 | 130 | .example-class-49 { 131 | float: left; 132 | } 133 | 134 | .example-class-50 { 135 | clear: both; 136 | } 137 | ``` -------------------------------------------------------------------------------- /packages/angular-mcp-server/src/lib/tools/ds/component-usage-graph/models/types.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { SourceFileLocation } from '@code-pushup/models'; 2 | 3 | export interface DependencyInfo { 4 | path: string; 5 | type: DependencyInfoType; 6 | resolved: boolean; 7 | resolvedPath?: string; 8 | componentName?: string; 9 | sourceFile?: string; 10 | source?: SourceFileLocation; 11 | } 12 | 13 | export interface FileInfo { 14 | type: string; 15 | size: number; 16 | dependencies: DependencyInfo[]; 17 | lastModified: number; 18 | componentName?: string; 19 | isAngularComponent?: boolean; 20 | source?: string; // Optional - not stored in optimized version to save memory 21 | } 22 | 23 | export type ComponentUsageGraphResult = Record<string, FileInfo>; 24 | 25 | export interface BuildComponentUsageGraphOptions { 26 | cwd: string; 27 | directory: string; 28 | workspaceRoot?: string; 29 | } 30 | 31 | export interface ComponentMetadata { 32 | className: string; 33 | } 34 | 35 | export interface ComponentGroup { 36 | componentFile?: [string, FileInfo]; 37 | relatedFiles: [string, FileInfo][]; 38 | hasReverseDeps: boolean; 39 | } 40 | 41 | export type DependencyInfoType = 42 | | 'import' 43 | | 'require' 44 | | 'dynamic-import' 45 | | 'css-import' 46 | | 'asset' 47 | | 'external' 48 | | 'reverse-dependency'; 49 | 50 | export type FileExtension = 51 | | '.ts' 52 | | '.js' 53 | | '.jsx' 54 | | '.tsx' 55 | | '.css' 56 | | '.scss' 57 | | '.sass' 58 | | '.less' 59 | | '.html'; 60 | 61 | export type FileType = 62 | | 'typescript' 63 | | 'typescript-react' 64 | | 'javascript' 65 | | 'javascript-react' 66 | | 'css' 67 | | 'scss' 68 | | 'sass' 69 | | 'less' 70 | | 'template'; 71 | 72 | // Legacy aliases for backward compatibility 73 | export type DependencyGraphResult = ComponentUsageGraphResult; 74 | export type BuildDependencyGraphOptions = BuildComponentUsageGraphOptions; 75 | ```