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

[labs/analyzer] More analysis coverage for gen-manifest #3529

Merged
merged 26 commits into from Jan 20, 2023
Merged

Conversation

kevinpschaaf
Copy link
Member

@kevinpschaaf kevinpschaaf commented Dec 16, 2022

This PR includes the following:

  • CLI improvements:
    • Print generate command diagnostic errors more gracefully to CLI - 8d7b0e9
    • Add support for --exclude options (e.g. exclude tests): 7fac64c) - important for excluding test files from e.g. manifest or wrapper generation
  • Add more analysis support and CEM emit:
    • TS enum type variables: bb8ba31
    • description, summary, and deprecated for all models: de76b2e
    • module-level description & summary: 32145ba
    • ClassField and ClassMethod: 3fd322c
  • Also introduces ...IfNotEmpty() helpers in gen-manifest to trim down the JSON size by only including optional fields when they have with meaningful values (e.g. non-empty string, non-empty array, true boolean, etc.)

This PR successfully builds the following manifest for MWC: custom-elements.json, after making the following minor MWC source changes:

  • menu/lib/menu.ts
    • export ActionDetail interface
    • remove unused? {SelectedDetail} type from @fires annotation

Addresses much of remaining items in #2993, with exception of:

  • FunctionDeclaration
  • MixinDeclaration / CustomElementMixinDeclaration
  • Attributes (of LitElementDeclaration)
  • ClassFields from accessors

@changeset-bot
Copy link

changeset-bot bot commented Dec 16, 2022

🦋 Changeset detected

Latest commit: 416e1f4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 7 packages
Name Type
@lit-labs/analyzer Minor
@lit-labs/cli Minor
@lit-labs/gen-manifest Minor
@lit-labs/gen-utils Patch
@lit-labs/gen-wrapper-angular Patch
@lit-labs/gen-wrapper-react Patch
@lit-labs/gen-wrapper-vue Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Contributor

github-actions bot commented Dec 16, 2022

📊 Tachometer Benchmark Results

Summary

⏳ Benchmarks are currently running. Results below are out of date.

nop-update

  • lit-html-kitchen-sink: unsure 🔍 -5% - +2% (-1.07ms - +0.54ms)
    this-change vs tip-of-tree

render

  • lit-element-list: 82.89ms - 88.09ms
  • lit-html-kitchen-sink: unsure 🔍 -6% - +3% (-2.05ms - +0.88ms)
    this-change vs tip-of-tree
  • lit-html-repeat: unsure 🔍 -13% - +10% (-1.70ms - +1.22ms)
    this-change vs tip-of-tree
  • lit-html-template-heavy: unsure 🔍 -2% - +2% (-0.87ms - +1.00ms)
    this-change vs tip-of-tree
  • reactive-element-list: unsure 🔍 -4% - +2% (-2.31ms - +1.04ms)
    this-change vs tip-of-tree

update

  • lit-element-list: 754.16ms - 769.85ms
  • lit-html-kitchen-sink: unsure 🔍 -3% - +3% (-2.37ms - +2.64ms)
    this-change vs tip-of-tree
  • lit-html-repeat: unsure 🔍 -3% - +1% (-10.41ms - +3.04ms)
    this-change vs tip-of-tree
  • lit-html-template-heavy: unsure 🔍 -1% - +1% (-1.00ms - +0.85ms)
    this-change vs tip-of-tree
  • reactive-element-list: unsure 🔍 -0% - +2% (-0.01ms - +11.55ms)
    this-change vs tip-of-tree

update-reflect

  • lit-element-list: 757.50ms - 767.72ms
  • reactive-element-list: unsure 🔍 -0% - +1% (-0.97ms - +9.38ms)
    this-change vs tip-of-tree

Results

⏳ Benchmarks are currently running. Results below are out of date.
lit-element-list

render

VersionAvg timevs
82.89ms - 88.09ms-

update

VersionAvg timevs
754.16ms - 769.85ms-

update-reflect

VersionAvg timevs
757.50ms - 767.72ms-
lit-html-kitchen-sink

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
30.50ms - 32.47ms-unsure 🔍
-6% - +3%
-2.05ms - +0.88ms
unsure 🔍
-6% - +2%
-1.90ms - +0.76ms
tip-of-tree
tip-of-tree
30.99ms - 33.15msunsure 🔍
-3% - +7%
-0.88ms - +2.05ms
-unsure 🔍
-4% - +4%
-1.39ms - +1.42ms
previous-release
previous-release
31.16ms - 32.95msunsure 🔍
-2% - +6%
-0.76ms - +1.90ms
unsure 🔍
-4% - +4%
-1.42ms - +1.39ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
81.20ms - 84.70ms-unsure 🔍
-3% - +3%
-2.37ms - +2.64ms
unsure 🔍
-2% - +5%
-1.52ms - +3.83ms
tip-of-tree
tip-of-tree
81.02ms - 84.61msunsure 🔍
-3% - +3%
-2.64ms - +2.37ms
-unsure 🔍
-2% - +5%
-1.68ms - +3.72ms
previous-release
previous-release
79.77ms - 83.82msunsure 🔍
-5% - +2%
-3.83ms - +1.52ms
unsure 🔍
-4% - +2%
-3.72ms - +1.68ms
-

nop-update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
21.90ms - 22.95ms-unsure 🔍
-5% - +2%
-1.07ms - +0.54ms
unsure 🔍
-4% - +3%
-0.86ms - +0.59ms
tip-of-tree
tip-of-tree
22.08ms - 23.30msunsure 🔍
-2% - +5%
-0.54ms - +1.07ms
-unsure 🔍
-3% - +4%
-0.66ms - +0.92ms
previous-release
previous-release
22.06ms - 23.06msunsure 🔍
-3% - +4%
-0.59ms - +0.86ms
unsure 🔍
-4% - +3%
-0.92ms - +0.66ms
-
lit-html-repeat

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
11.94ms - 12.57ms-unsure 🔍
-13% - +10%
-1.70ms - +1.22ms
unsure 🔍
-2% - +6%
-0.29ms - +0.74ms
tip-of-tree
tip-of-tree
11.07ms - 13.92msunsure 🔍
-10% - +14%
-1.22ms - +1.70ms
-unsure 🔍
-8% - +16%
-1.02ms - +1.94ms
previous-release
previous-release
11.62ms - 12.44msunsure 🔍
-6% - +2%
-0.74ms - +0.29ms
unsure 🔍
-15% - +8%
-1.94ms - +1.02ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
293.26ms - 299.38ms-unsure 🔍
-3% - +1%
-10.41ms - +3.04ms
unsure 🔍
-3% - +1%
-10.29ms - +1.92ms
tip-of-tree
tip-of-tree
294.02ms - 305.99msunsure 🔍
-1% - +4%
-3.04ms - +10.41ms
-unsure 🔍
-3% - +2%
-8.49ms - +7.48ms
previous-release
previous-release
295.22ms - 305.79msunsure 🔍
-1% - +3%
-1.92ms - +10.29ms
unsure 🔍
-2% - +3%
-7.48ms - +8.49ms
-
lit-html-template-heavy

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
52.72ms - 53.90ms-unsure 🔍
-2% - +2%
-0.87ms - +1.00ms
unsure 🔍
-2% - +1%
-0.99ms - +0.75ms
tip-of-tree
tip-of-tree
52.52ms - 53.97msunsure 🔍
-2% - +2%
-1.00ms - +0.87ms
-unsure 🔍
-2% - +1%
-1.14ms - +0.78ms
previous-release
previous-release
52.80ms - 54.06msunsure 🔍
-1% - +2%
-0.75ms - +0.99ms
unsure 🔍
-1% - +2%
-0.78ms - +1.14ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
106.79ms - 108.11ms-unsure 🔍
-1% - +1%
-1.00ms - +0.85ms
unsure 🔍
-0% - +1%
-0.43ms - +1.18ms
tip-of-tree
tip-of-tree
106.88ms - 108.17msunsure 🔍
-1% - +1%
-0.85ms - +1.00ms
-unsure 🔍
-0% - +1%
-0.34ms - +1.24ms
previous-release
previous-release
106.62ms - 107.53msunsure 🔍
-1% - +0%
-1.18ms - +0.43ms
unsure 🔍
-1% - +0%
-1.24ms - +0.34ms
-
reactive-element-list

render

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
53.04ms - 55.15ms-unsure 🔍
-4% - +2%
-2.31ms - +1.04ms
unsure 🔍
-4% - +2%
-1.99ms - +0.99ms
tip-of-tree
tip-of-tree
53.43ms - 56.04msunsure 🔍
-2% - +4%
-1.04ms - +2.31ms
-unsure 🔍
-3% - +3%
-1.54ms - +1.81ms
previous-release
previous-release
53.54ms - 55.65msunsure 🔍
-2% - +4%
-0.99ms - +1.99ms
unsure 🔍
-3% - +3%
-1.81ms - +1.54ms
-

update

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
759.00ms - 767.98ms-unsure 🔍
-0% - +2%
-0.01ms - +11.55ms
unsure 🔍
-0% - +1%
-2.20ms - +9.52ms
tip-of-tree
tip-of-tree
754.08ms - 761.36msunsure 🔍
-2% - -0%
-11.55ms - +0.01ms
-unsure 🔍
-1% - +0%
-7.35ms - +3.13ms
previous-release
previous-release
756.06ms - 763.60msunsure 🔍
-1% - +0%
-9.52ms - +2.20ms
unsure 🔍
-0% - +1%
-3.13ms - +7.35ms
-

update-reflect

VersionAvg timevs this-change
vs tip-of-tree
tip-of-tree
vs previous-release
previous-release
this-change
779.13ms - 787.56ms-unsure 🔍
-0% - +1%
-0.97ms - +9.38ms
unsure 🔍
-0% - +1%
-3.73ms - +7.12ms
tip-of-tree
tip-of-tree
776.13ms - 782.15msunsure 🔍
-1% - +0%
-9.38ms - +0.97ms
-unsure 🔍
-1% - +0%
-7.06ms - +2.04ms
previous-release
previous-release
778.24ms - 785.06msunsure 🔍
-1% - +0%
-7.12ms - +3.73ms
unsure 🔍
-0% - +1%
-2.04ms - +7.06ms
-

tachometer-reporter-action v2 for Benchmarks

@sorvell
Copy link
Member

sorvell commented Jan 6, 2023

I noticed that some of the privacy labels in custom-elements.json are incorrect. For example firstUpdated is noted as public. Is that because the source is not properly including override or does privacy need to be brought forward from the base class?

packages/labs/analyzer/src/lib/model.ts Show resolved Hide resolved
}
const {name, type, description, summary} = nameDescSummary.groups!;
const info: NamedTypedJSDocInfo = {name, type};
if (summary !== undefined) {
Copy link
Member

Choose a reason for hiding this comment

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

Can type be undefined as well?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, if the regex does't find a type string in the {...}, it just goes in as undefined

* * @slot name: summary
* * @slot name - summary
* * @slot name description
* * @slot name: description
Copy link
Member

Choose a reason for hiding this comment

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

Why is a : separator supported here but not in parseNamedTypedJSDocInfo?

Copy link
Member Author

Choose a reason for hiding this comment

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

After consulting with Justin, we removed supporting : as a separator altogether. My best guess is I misinterpreted a regex originally implemented by Justin as intending to include : and started supporting that everywhere.

packages/labs/analyzer/src/lib/model.ts Show resolved Hide resolved
Copy link
Collaborator

@justinfagnani justinfagnani left a comment

Choose a reason for hiding this comment

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

I'm about halfway through

@@ -4,14 +4,21 @@ import * as path from 'path';
import {DiagnosticsError} from './errors.js';
import {Analyzer} from './analyzer.js';

export interface AnalyzerOptions {
exclude?: string[];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add comment. Are these globs?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

Yeah, globs. Useful for excluding things source like test folders that might otherwise be included in a project's tsconfig.

It might be interesting (later on, not now) to analyze a package.json's files or exports and filter down the input source to analyze based on that.

However ^^ highlights an unsolved problem in the analyzer right now (will likely need to be resolved via config) which is that just because we can determine that src/foo.ts gets output by tsc to build/foo.js doesn't mean that's where the file is ultimately importable from, since additional transforms outside of ts can (and often do) further process that into another location that is ultimately published (i.e. we have tsc output to ./development/*.js and process that further with rollup to ./*.js).

This is something we're going to hit pretty quickly when we start testing against real-world packages. It's been on my radar, but went ahead and added #3578 to track it.

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 there are a few things we can do here that won't cover every case, but will cover a lot of them... I'll comment on the issue.

The biggest thing that clicked for me here is that these augment the tsconfig exclude setting.

@@ -55,6 +65,7 @@ export const createPackageAnalyzer = (packagePath: AbsolutePath) => {
moduleResolution: 'node',
},
include: ['**/*.js'],
exclude: options.exclude ? [...options.exclude] : [],
Copy link
Collaborator

Choose a reason for hiding this comment

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

why do you need to make a copy? Does parseJsonConfigFileContent modify the array?

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed to options.exclude ?? []. Was probably just being paranoid I guess?

* - If the first statement has more than one JSDoc block, we collect all
* but the last and use those, regardless of whether they contain one
* of the above module-designating tags (the last one is assumed to belong
* to the first statement).
Copy link
Collaborator

Choose a reason for hiding this comment

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

nice comment

packages/labs/analyzer/src/lib/model.ts Show resolved Hide resolved
packages/labs/analyzer/src/lib/model.ts Outdated Show resolved Hide resolved
packages/labs/analyzer/src/lib/model.ts Show resolved Hide resolved
packages/labs/analyzer/src/lib/model.ts Show resolved Hide resolved
packages/labs/analyzer/src/lib/utils.ts Outdated Show resolved Hide resolved
@@ -406,11 +485,24 @@ export interface LitElementExport extends LitElementDeclaration {
tagname: string;
}

export interface ReactiveProperty {
export interface PropertyLike extends NodeJSDocInfo {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this heritage seems a little weird to me at first read. Is JSDocInfo the right name? It seems to imply something specifically analyzed from JSDoc, but properties won't always have JSDoc comments and the main thing is that they're described - we just happened to get that info from JSDoc. So maybe name them Described, Named, etc?

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair, I mean those things can't be inferred from source other than from JSDoc comments, which is how I got to that naming, but I renamed them to be more generic as suggested.

this.inheritedFrom = init.inheritedFrom;
this.source = init.source;
this.type = init.type;
this.default = init.default;
Copy link
Collaborator

Choose a reason for hiding this comment

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

🏌️‍♂️ I once went looking for a more concise pattern for this, and this is the best I could come up with:

({
  static: this.static,
  privacy: this.privacy,
  inheritedFrom: this.inheritedFrom,
  source: this.source,
  type: this.type,
  default: this.default,
} = init);

It removes the repeated init., but I'm not sure it's shorter enough to matter, and some people might find it weird.

Copy link
Member

Choose a reason for hiding this comment

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

clever

Comment on lines 28 to 31
v !== undefined &&
(typeof v !== 'boolean' || v === true) &&
(typeof v !== 'string' || v.length > 0) &&
(!Array.isArray(v) || v.length);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This logic was a little hard for me to parse. What about writing the positive cases?

Suggested change
v !== undefined &&
(typeof v !== 'boolean' || v === true) &&
(typeof v !== 'string' || v.length > 0) &&
(!Array.isArray(v) || v.length);
v === true || ((typeof v === 'string' || Array.isArray(v)) && v.length > 0);

and add a comment on why true is considered not empty? I don't see that case in the expressions you replaced with the function call.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 117 to 121
return {
...pickIfNotEmpty(info, 'description'),
...pickIfNotEmpty(info, 'summary'),
...pickIfNotEmpty(info, 'deprecated'),
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

undefined properties aren't written out with JSON.stringify(), so might it be nicer to not use spread to write a single value, and do something like:

Suggested change
return {
...pickIfNotEmpty(info, 'description'),
...pickIfNotEmpty(info, 'summary'),
...pickIfNotEmpty(info, 'deprecated'),
};
return {
description: removeEmpty(info.description),
summary: removeEmpty(info.summary),
deprecated: removeEmpty(info.deprecated),
};

Or, are the all the properties in NodeJSDocInfo always going to be used? Could you transform them all?

Suggested change
return {
...pickIfNotEmpty(info, 'description'),
...pickIfNotEmpty(info, 'summary'),
...pickIfNotEmpty(info, 'deprecated'),
};
return removeEmpties(info);

Copy link
Member Author

Choose a reason for hiding this comment

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

undefined properties aren't written out with JSON.stringify()

OMG! 🤦 Yeah I think I knew this but totally forgot about it... definitely better to leave the key and avoid the spread. Will refactor.

Copy link
Member Author

Choose a reason for hiding this comment

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

Refactored into

  • ifNotEmpty(v)
  • transformIfNotEmpty(v, transform)
    which always return a meaningful value or undefined, and are assigned directly to keys (not spread)

// // TODO: ClassField
// // TODO: ClassMethod
// ],
...useIfNotEmpty('members', [
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similar comment about undefined being ok here, so we don't need spread.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

return {};
}
const jsDocs = firstChild.getChildren().filter(ts.isJSDoc);
const moduleJSDocs = jsDocs.slice(0, jsDocs.length > 1 ? -1 : 1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Might be worth some comments in here

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 36 to 39
v === undefined ||
(typeof v === 'boolean' && v === false) ||
(typeof v === 'string' && v.length === 0) ||
(Array.isArray(v) && v.length === 0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This can still be simplified, ie with === the typeof v === 'boolean' isn't necessary, and the undefined check isn't needed:

Suggested change
v === undefined ||
(typeof v === 'boolean' && v === false) ||
(typeof v === 'string' && v.length === 0) ||
(Array.isArray(v) && v.length === 0)
v === false || ((typeof v === 'string' || Array.isArray(v)) && v.length === 0)

@kevinpschaaf kevinpschaaf merged commit 389d0c5 into main Jan 20, 2023
@kevinpschaaf kevinpschaaf deleted the cem-moar branch January 20, 2023 00:51
@lit-robot lit-robot mentioned this pull request Mar 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants