Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugin-doc-coverage): add doc-coverage-plugin to analyze documentation in ts/js projects #896

Open
wants to merge 53 commits into
base: main
Choose a base branch
from

Conversation

aramirezj
Copy link

@aramirezj aramirezj commented Dec 17, 2024

This PR includes:

  • documentation
  • Unit testing
    • Source Files are mocked
  • Integration tests
    • Test case for every audit in a small codebase
  • e2e
  • usage of the plugin in the repository

Closes #908

Report Summary

Overview of Audit Groups, Audit Counts, and Diagnostic Issue Types.

📁 Group Name 📋 Audits 🐞 Issues
documentation-coverage classes-coverage, methods-coverage, functions-coverage, interfaces-coverage, variables-coverage, properties-coverage, types-coverage, enums-coverage Missing documentation
------------------------ ------------------------------------------------- -------------------
1 Group Total 8 Audits Total 1 Issue Type Total

@github-actions github-actions bot added 📖 Project documentation improvements or additions to the project documentation 🔬 testing writing tests 🛠️ tooling labels Dec 17, 2024
Copy link

github-actions bot commented Dec 17, 2024

Code PushUp

😟 Code PushUp report has regressed – compared target commit 4777a83 with source commit 4777a83.

🏷️ Categories

🏷️ Category ⭐ Previous score ⭐ Current score 🔄 Score change
Performance 🟡 58 🔴 50 ↓ −8.7
Code coverage 🟢 91 🟢 91
Security 🟡 81 🟡 81
Updates 🟡 80 🟡 80
Accessibility 🟢 91 🟢 91
Best Practices 🟢 100 🟢 100
SEO 🟡 61 🟡 61
Bug prevention 🟢 100 🟢 100
Code style 🟢 100 🟢 100
👎 1 group regressed, 👎 5 audits regressed, 12 audits changed without impacting score

🗃️ Groups

🔌 Plugin 🗃️ Group ⭐ Previous score ⭐ Current score 🔄 Score change
Lighthouse Performance 🟡 58 🔴 50 ↓ −8.7

16 other groups are unchanged.

🛡️ Audits

🔌 Plugin 🛡️ Audit 📏 Previous value 📏 Current value 🔄 Value change
Lighthouse Speed Index 🟨 4.6 s 🟥 6.6 s ↑ +44.3 %
Lighthouse Largest Contentful Paint 🟨 2.9 s 🟨 3.6 s ↑ +20.7 %
Lighthouse Time to Interactive 🟥 11.1 s 🟥 13.7 s ↑ +24.4 %
Lighthouse First Contentful Paint 🟨 2.8 s 🟥 3.0 s ↑ +8.9 %
Lighthouse Total Blocking Time 🟥 3,070 ms 🟥 3,910 ms ↑ +27.3 %
Lighthouse Avoids enormous network payloads 🟩 Total size was 1,821 KiB 🟩 Total size was 1,825 KiB ↑ +0.2 %
Lighthouse Metrics 🟩 100% 🟩 100% ↑ +24.4 %
Lighthouse Minimizes main-thread work 🟥 12.0 s 🟥 14.5 s ↑ +21 %
Lighthouse JavaScript execution time 🟥 5.6 s 🟥 6.7 s ↑ +20.5 %
Lighthouse Max Potential First Input Delay 🟥 1,460 ms 🟥 2,020 ms ↑ +38.4 %
Lighthouse Server Backend Latencies 🟩 80 ms 🟩 300 ms ↑ +298.2 %
Lighthouse Reduce unused CSS 🟥 Potential savings of 66 KiB 🟥 Potential savings of 66 KiB ↑ +40.6 %
Lighthouse Uses efficient cache policy on static assets 🟨 28 resources found 🟨 28 resources found ↑ +0.1 %
Lighthouse Initial server response time was short 🟩 Root document took 400 ms 🟩 Root document took 370 ms ↓ −6.9 %
Lighthouse Eliminate render-blocking resources 🟥 Potential savings of 1,130 ms 🟥 Potential savings of 1,150 ms ↑ +1.7 %
Lighthouse Network Round Trip Times 🟩 30 ms 🟩 30 ms ↓ −18.5 %
Lighthouse Avoids an excessive DOM size 🟥 2,133 elements 🟥 2,134 elements ↑ +0.1 %

571 other audits are unchanged.

@aramirezj aramirezj changed the title feat(plugin-doc-coverage): add mvp version of a plugin doc coverage based on compodoc feat(plugin-doc-coverage): add mvp version of a plugin doc coverage based on typedoc and TBD Dec 17, 2024
@aramirezj aramirezj marked this pull request as ready for review December 20, 2024 18:53
@aramirezj aramirezj changed the title feat(plugin-doc-coverage): add mvp version of a plugin doc coverage based on typedoc and TBD feat(plugin-doc-coverage): add doc-coverage-plugin to analyze documentation in ts/js projects Dec 21, 2024
Copy link
Collaborator

@BioPhoton BioPhoton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WONDERFUL!

cc @vmasek and @matejchalk

BioPhoton
BioPhoton previously approved these changes Dec 23, 2024
BioPhoton
BioPhoton previously approved these changes Dec 23, 2024
@vmasek
Copy link
Collaborator

vmasek commented Jan 2, 2025

@aramirezj thank you for putting this plugin together, we did discuss it on refinement today and we are exited to adopt it as one of the plugins that should live in this monorepo (there are plugins that we purposely do not maintain here as they do not meet the JS/TS tech stack).

We are also looking forward to setup automated docs generation, so it makes even more sense to start tracking it with plugin.

There are some things we need to figure out before we merge, mainly related to testing. We are thinking if there should already be a simple e2e tests similar to for the ESLint e2e exaple, is it something you'd also like to contribute?

@aramirezj aramirezj mentioned this pull request Jan 2, 2025
6 tasks
@aramirezj
Copy link
Author

@aramirezj thank you for putting this plugin together, we did discuss it on refinement today and we are exited to adopt it as one of the plugins that should live in this monorepo (there are plugins that we purposely do not maintain here as they do not meet the JS/TS tech stack).

We are also looking forward to setup automated docs generation, so it makes even more sense to start tracking it with plugin.

There are some things we need to figure out before we merge, mainly related to testing. We are thinking if there should already be a simple e2e tests similar to for the ESLint e2e exaple, is it something you'd also like to contribute?

I am glad to hear that! And sure! I will work into that ^^

nodesCount: 1,
issues: [
{
file: expect.stringContaining('classes-coverage'),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
file: expect.stringContaining('classes-coverage'),
file: expect.stringContaining('classes-coverage.ts'),

To make sure it is at the end of the file, and not a different file with same path segment.

This is applicable to multiple places.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd even say it could be stringEndingWith('classes-coverage.ts')

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added expect.stringMatching(/properties-coverage\.ts$/) that it should do the trick

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aramirezj with the latest merge we have OS agnostic path helpers in the codebase:
https://github.com/code-pushup/cli/blob/main/testing/test-setup/src/lib/extend/path.matcher.unit.test.ts

Let's use them.

Copy link
Author

@aramirezj aramirezj Jan 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!, If any of you want to resolve the conversation and approve it ^^ @BioPhoton @vmasek

Copy link
Collaborator

@matejchalk matejchalk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your contribution, this plugin looks really promising ❤️

My main quibble is with the outer namings, but also left some suggestions for the source code.

@@ -0,0 +1,42 @@
{
"name": "@code-pushup/doc-coverage-plugin",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the plugin name is little bit verbose and too similar to existing coverage-plugin. Naming it @code-pushup/jsdocs-plugin would be better, IMHO. What do you think?

(BTW @nx/workspace:move should help with the renamings.)

Comment on lines +98 to +99
slug: 'doc-coverage-cat',
title: 'Documentation coverage',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use a -cat suffix for any other category slugs, it's implicit from context.

Also, it's best for category names to be as short as possible, since they are very high-level. How about just Documentation/docs?

Suggested change
slug: 'doc-coverage-cat',
title: 'Documentation coverage',
slug: 'docs',
title: 'Documentation',

Comment on lines +43 to +52
await docCoverageCoreConfig({
sourceGlob: [
'packages/**/src/**/*.ts',
'!packages/**/node_modules',
'!packages/**/{mocks,mock}',
'!**/*.{spec,test}.ts',
'!**/implementation/**',
'!**/internal/**',
],
}),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ESLint plugin has a similar parameter called patterns, would be nice to have consistent naming.

Suggested change
await docCoverageCoreConfig({
sourceGlob: [
'packages/**/src/**/*.ts',
'!packages/**/node_modules',
'!packages/**/{mocks,mock}',
'!**/*.{spec,test}.ts',
'!**/implementation/**',
'!**/internal/**',
],
}),
await docCoverageCoreConfig({
patterns: [
'packages/**/src/**/*.ts',
'!packages/**/node_modules',
'!packages/**/{mocks,mock}',
'!**/*.{spec,test}.ts',
'!**/implementation/**',
'!**/internal/**',
],
}),

Since I'm guessing only these globs are needed for most usages, the plugin parameter could also enable a shorthand (again, similar to ESLint plugin):

Suggested change
await docCoverageCoreConfig({
sourceGlob: [
'packages/**/src/**/*.ts',
'!packages/**/node_modules',
'!packages/**/{mocks,mock}',
'!**/*.{spec,test}.ts',
'!**/implementation/**',
'!**/internal/**',
],
}),
await docCoverageCoreConfig([
'packages/**/src/**/*.ts',
'!packages/**/node_modules',
'!packages/**/{mocks,mock}',
'!**/*.{spec,test}.ts',
'!**/implementation/**',
'!**/internal/**',
),


- `value`: The value is the number of undocumented nodes -> 4
- `displayValue`: `${value} undocumented ${type}` -> 4 undocumented functions
- `score`: 0.5 -> total nodes 8 undocumented 4 -> 8/4
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- `score`: 0.5 -> total nodes 8 undocumented 4 -> 8/4
- `score`: 0.5 -> total nodes 8, undocumented 4 -> 4/8

Comment on lines +143 to +150
export const docCoverageCoreConfig = async (
config: DocCoveragePluginConfig,
): Promise<CoreConfig> => {
return {
plugins: [docCoveragePlugin(config)],
categories: getDocCoverageCategories(config),
};
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't need to be async:

Suggested change
export const docCoverageCoreConfig = async (
config: DocCoveragePluginConfig,
): Promise<CoreConfig> => {
return {
plugins: [docCoveragePlugin(config)],
categories: getDocCoverageCategories(config),
};
};
export const docCoverageCoreConfig = (
config: DocCoveragePluginConfig,
): CoreConfig => {
return {
plugins: [docCoveragePlugin(config)],
categories: getDocCoverageCategories(config),
};
};

Comment on lines +20 to +22
} satisfies DocumentationData & {
coverage: number;
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DocumentationData & { coverage: number } type is used a lot. Maybe worth creating a type alias for it?

): AuditOutputs {
return Object.entries(coverageResult)
.filter(([type]) => {
const auditSlug = `${type}-coverage`;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although the transformation is simple, I think it would be worth extracting to some coverageTypeToAuditSlug function. Then the slug pattern can be changed in one place if needed.

},
],
},
} as unknown as DocumentationCoverageReport;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of as unknown as T, since it loses all type-safety and autocomplete. In this case it's also unnecessary:

Suggested change
} as unknown as DocumentationCoverageReport;
} as DocumentationCoverageReport;

Comment on lines +30 to +51
export function calculateCoverage(result: DocumentationReport) {
return Object.fromEntries(
Object.entries(result).map(([key, value]) => {
const type = key as CoverageType;
return [
type,
{
coverage:
value.nodesCount === 0
? 100
: Number(
((1 - value.issues.length / value.nodesCount) * 100).toFixed(
2,
),
),
issues: value.issues,
nodesCount: value.nodesCount,
},
];
}),
) as DocumentationCoverageReport;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "better Object.entries/Object.fromEntries" can help here also:

Suggested change
export function calculateCoverage(result: DocumentationReport) {
return Object.fromEntries(
Object.entries(result).map(([key, value]) => {
const type = key as CoverageType;
return [
type,
{
coverage:
value.nodesCount === 0
? 100
: Number(
((1 - value.issues.length / value.nodesCount) * 100).toFixed(
2,
),
),
issues: value.issues,
nodesCount: value.nodesCount,
},
];
}),
) as DocumentationCoverageReport;
}
export function calculateCoverage(
result: DocumentationReport,
): DocumentationReport {
return objectFromEntries(
objectToEntries(result).map(([key, value]) => [
key,
{
coverage:
value.nodesCount === 0
? 100
: Number(
((1 - value.issues.length / value.nodesCount) * 100).toFixed(2),
),
issues: value.issues,
nodesCount: value.nodesCount,
},
]),
);
}

Comment on lines +58 to +80
export function getCoverageTypeFromKind(kind: SyntaxKind): CoverageType {
switch (kind) {
case SyntaxKind.ClassDeclaration:
return 'classes';
case SyntaxKind.MethodDeclaration:
return 'methods';
case SyntaxKind.FunctionDeclaration:
return 'functions';
case SyntaxKind.InterfaceDeclaration:
return 'interfaces';
case SyntaxKind.EnumDeclaration:
return 'enums';
case SyntaxKind.VariableStatement:
case SyntaxKind.VariableDeclaration:
return 'variables';
case SyntaxKind.PropertyDeclaration:
return 'properties';
case SyntaxKind.TypeAliasDeclaration:
return 'types';
default:
throw new Error(`Unsupported syntax kind: ${kind}`);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mapping duplicates the SyntaxKindToStringLiteral type. It would be better to have a single source of truth for this mapping.

// shared constant acts as source of truth
export const SYNTAX_COVERAGE_MAP = {
  [SyntaxKind.ClassDeclaration]: 'classes',
  [SyntaxKind.MethodDeclaration]: 'methods',
  [SyntaxKind.FunctionDeclaration]: 'functions',
  [SyntaxKind.InterfaceDeclaration]: 'interfaces',
  [SyntaxKind.EnumDeclaration]: 'enums',
  [SyntaxKind.VariableDeclaration]: 'variables',
  [SyntaxKind.PropertyDeclaration]: 'properties',
  [SyntaxKind.TypeAliasDeclaration]: 'types',
} as const;

// `typeof` gets you the mapped type
export type CoverageType =
  (typeof SYNTAX_COVERAGE_MAP)[keyof typeof SYNTAX_COVERAGE_MAP];

// can be reused for runtime conversion also
export function getCoverageTypeFromKind(kind: SyntaxKind): CoverageType {
  const map = new Map<SyntaxKind, CoverageType>(
    objectToEntries(SYNTAX_COVERAGE_MAP),
  );
  const type = map.get(kind);
  if (!type) {
    throw new Error(`Unsupported syntax kind: ${kind}`);
  }
  return type;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📖 Project documentation improvements or additions to the project documentation 🔬 testing writing tests 🛠️ tooling
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Documentation Coverage Plugin
4 participants