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

improvement(merge-tree): Use recommended eslint config #21684

Merged
merged 22 commits into from
Jul 11, 2024

Conversation

jzaffiro
Copy link
Contributor

@jzaffiro jzaffiro commented Jun 27, 2024

Updates the eslint config from minimal-deprecated to recommended, and addresses the resulting violations.

AB#3014

@github-actions github-actions bot added area: dds Issues related to distributed data structures base: main PRs targeted against main branch labels Jun 27, 2024
@github-actions github-actions bot added the public api change Changes to a public API label Jun 28, 2024
@jzaffiro jzaffiro marked this pull request as ready for review June 28, 2024 23:51
@jzaffiro jzaffiro requested review from a team as code owners June 28, 2024 23:51
@@ -77,7 +77,7 @@ export abstract class BaseSegment implements ISegment {
// (undocumented)
splitAt(pos: number): ISegment | undefined;
// (undocumented)
abstract toJSONObject(): any;
abstract toJSONObject(): unknown;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if this is better than suppressing the errors for any json stuff...

Copy link
Contributor

@Josmithr Josmithr Jun 29, 2024

Choose a reason for hiding this comment

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

If it affects the API surface in a way that might break consumers, we should suppress the error. This is a case where we would need to put it back to any.

We should fix other violations where they can be fixed without too much effort, but the primary goal is to prevent new violations of our rules. So feel free to disable rules anywhere it would break the API, or if it would require substantial refactoring to accommodate the changes. We should leave TODO comments in any places where we disable though.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to commenting wherever we disable so we don't lose the context and don't normalize suppression.

@@ -3,9 +3,10 @@
* Licensed under the MIT License.
*/

/* eslint-disable unicorn/no-useless-spread */
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This rule has some false positives - we actually do need the spread operator, since the result of the mapping made by the autofix is an Iterable, not an array.
sindresorhus/eslint-plugin-unicorn#2018

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's leave a comment in the code explaining this next to the disable comment.

Copy link
Contributor

Choose a reason for hiding this comment

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

We can do the same thing without the spread operator.
const pending = [...client.mergeTree.pendingSegments.map((n) => n.data)];
can be changed to
const pending = Array.from(client.mergeTree.pendingSegments.map((n) => n.data));

Copy link
Contributor

Choose a reason for hiding this comment

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

Or, since all we do is shift the array, we could replace that with just iterating and skip the array entirely?

const pending = client.mergeTree.pendingSegments.map((n) => n.data);
const pendingIterator: Iterator<SegmentGroup, undefined> = pending[Symbol.iterator]();
opList = oldops.map((op) => ({
	op: client.regeneratePendingOp(op.op, pendingIterator.next().value!),
	refSeq: client.getCurrentSeq(),
}));
Copy link
Contributor

Choose a reason for hiding this comment

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

The Array.from with 2 arguments seems good. I think that's a bit different from what I suggested to Jillian while she was looking at it, but a small variant on it was rejected by a 'prefer-spread' type rule.

FWIW, I don't really like having a rule in our recommended config with such a large gap like this. It has an autofixer which will make correct code incorrect (which was exactly what happened to Jillian here), presumably because it assumes the return of .map is already an array without actually having typing knowledge.

Copy link
Contributor

Choose a reason for hiding this comment

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

In most such cases it probably complains at build time in which case it's not the worst, but I'm not sure that's always the case. IIRC @taylorsw04 or @noencke mentioned at some point getting toasted by linter autofixes in id-compressor that I think was also related to spread operators, but not sure if it was this rule or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW, I don't really like having a rule in our recommended config with such a large gap like this. It has an autofixer which will make correct code incorrect (which was exactly what happened to Jillian here), presumably because it assumes the return of .map is already an array without actually having typing knowledge.

That seems like a good reason to disable the rule, yeah. Definitely feel free to disable in the config here for this PR, since it's clearly causing problems. I can get a PR up disabling it in our shared config.

Copy link
Contributor Author

@jzaffiro jzaffiro Jul 5, 2024

Choose a reason for hiding this comment

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

The Array.from with 2 arguments seems good. I think that's a bit different from what I suggested to Jillian while she was looking at it, but a small variant on it was rejected by a 'prefer-spread' type rule.

@Abe27342 @titrindl The Array.from is still getting rejected, so I'll leave the spread operator.

Copy link
Contributor

Choose a reason for hiding this comment

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

PR disabling the rule in the base config: #21804

@@ -262,9 +271,13 @@ export class LocalReferenceCollection {
}

/**
* Returns an iterator.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we be more specific here? What is the returned iterator intended to iterate?

@@ -1170,17 +1205,19 @@ export class MergeTree {
const locallyMovedSegments = this.locallyMovedSegments.get(localMovedSeq);

if (locallyMovedSegments) {
locallyMovedSegments.segments.forEach((segment: ISegmentLeaf) => {
segment.localMovedSeq = undefined;
for (const segment of locallyMovedSegments.segments) {
Copy link
Contributor

Choose a reason for hiding this comment

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

The typing difference might be a good reason to revert this particular change and disable the lint rule (with a comment explaining why).

if (this.properties) {
jseg.props = { ...this.properties };
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's document why we can't convert this yet and/or leave a TODO to come back later and clean this up.

* @internal
*/
export const compareNumbers = (a: number, b: number) => a - b;
export const compareNumbers = (a: number, b: number): number => a - b;
Copy link
Contributor

Choose a reason for hiding this comment

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

(Not for this PR) I'm surprised we need to export these from the package, even if just @internal. Curious if anyone knows why these are currently being shared.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like they're used in partialLengths and a few tests, but I'm not sure if there's a specific reason to export them from here over defining them when needed. @Abe27342 thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

it does look like they are exported from the package, so the tag does nothing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@anthony-murphy should it be removed then? I'm tracking some other cleanup with AB#8760, so I can do it as part of that work if so.

Copy link
Contributor

Choose a reason for hiding this comment

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

at minimum, the tag should be removed :-). We could also consider inlining them in the test cases we use them in if they're not strictly related (the comparisons are the idiomatic ones), but i'm kinda indifferent there.

Copy link
Contributor

Choose a reason for hiding this comment

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

whatever you do here, follow-up pr is obviously fine. pre-existing.

@@ -121,6 +121,7 @@ export interface IMergeTreeInsertMsg extends IMergeTreeDelta {
relativePos1?: IRelativePosition;
pos2?: number;
relativePos2?: IRelativePosition;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we leave explanations/TODOs for these disables?

assert(
maxCount <= 16 && actualCount <= maxCount,
0x3f0 /* count must be less than max, and max must be 16 or less */,
);

const ordinalWidth = 1 << (maxCount - actualCount);
let ordinal: string;
if (previousOrdinal === undefined) {
ordinal = parentOrdinal + String.fromCharCode(ordinalWidth - 1);
if (previousOrdinal === undefined || previousOrdinal === "") {
Copy link
Contributor

Choose a reason for hiding this comment

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

This appears to a behavioral change. Is this intentional / safe to make?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is intentional - to find the parent ordinal, the tests use fromCodePoint, which returns an empty string if the result of making the elements passed in into a string has a length of 0. fromCharCode returned undefined, but the tests were not accurately determining if there was a parentOrdinal without the extra check.

Copy link
Contributor

Choose a reason for hiding this comment

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

gives me a minor amount of pause toward actually following this lint rule as per other comment. I'm pretty confident our tests exercise the cases we care about here though, so not a huge deal.

@@ -381,6 +381,8 @@ export class PartialSequenceLengths {
}

/**
* PartialLengths for the leaves of a given MergeBlock.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: This isn't clear to me. Might be helpful to write this out as a complete sentence for clarity.

@@ -140,7 +140,7 @@ describe("obliterate partial lengths", () => {
refSeq,
);
});

// here are tests failing w nouncheckedindexaccess
Copy link
Contributor

Choose a reason for hiding this comment

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

Was this intentional?

Copy link
Contributor

@Josmithr Josmithr left a comment

Choose a reason for hiding this comment

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

I left a few more suggestions / nitpicks, but overall looks good. Thanks for working through all of this - this was one of the biggest packages left to migrate!

jzaffiro and others added 2 commits July 9, 2024 15:03
Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com>
@tylerbutler tylerbutler changed the title Update merge tree config Jul 10, 2024
@tylerbutler tylerbutler changed the title improvement(merge-tree): Use strict eslint config Jul 10, 2024
@@ -320,6 +325,7 @@ export class AttributionCollection implements IAttributionCollection<Attribution
if (i !== 0 || !areEqualAttributionKeys(lastEntry, other.keys[i])) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.offsets.push(other.offsets[i]! + this.length);
// TODO: see line 305
Copy link
Contributor

Choose a reason for hiding this comment

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

hm?

Copy link
Contributor

Choose a reason for hiding this comment

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

@jzaffiro, just calling your attention here - was merged like this

// just compute an arbitrarily big ordinal
// we base it on the depth of the tree
// to ensure it is bigger than all ordinals in
// the tree, as each layer appends to the previous
return String.fromCharCode(0xffff).repeat(this.endpointSegmentProps().depth);
return String.fromCodePoint(0xffff).repeat(this.endpointSegmentProps().depth);
Copy link
Contributor

Choose a reason for hiding this comment

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

though it's not a huge deal and I think the behavior here is correct, I would have leaned toward disabling the lint rule when dealing with ordinals. The intent of this lint rule is probably "you probably don't support unicode correctly if you're using this and likely have bugs", but for ordinals we obviously aren't actually ever dealing with user input, we're just using it for dictionary order.

i think it's fine as is, but worth a comment. whatever you go with, just be consistent that all logic related to ordinals uses a consistent method.

return siblingExists;
};

const onLeaf = (
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would not remove onLeaf and continueFrom from their scope. this makes the code harder to read than before, even if it is technically not necessary to create a new function on each execution.

Copy link
Contributor

Choose a reason for hiding this comment

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

if they had informative names perhaps it'd be ok as well.

@Abe27342
Copy link
Contributor

feel free to address all my comments in a follow-up PR. I don't really want to look at this again and it'll be easier to audit that way anyway.

Co-authored-by: Abram Sanderson <Abram.sanderson@gmail.com>
@jzaffiro jzaffiro enabled auto-merge (squash) July 11, 2024 22:33
@jzaffiro jzaffiro merged commit d2d472b into microsoft:main Jul 11, 2024
30 checks passed
jzaffiro added a commit that referenced this pull request Jul 12, 2024
Minor improvements to #21684, including disabling
[prefer-code-point](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-code-point.md)
for ordinals, reverting function scope changes, and updating
documentation.

---------

Co-authored-by: Abram Sanderson <Abram.sanderson@gmail.com>
@jzaffiro jzaffiro deleted the updateMergeTreeConfig branch July 12, 2024 22:18
RishhiB pushed a commit to RishhiB/FluidFramework-1 that referenced this pull request Jul 18, 2024
Updates the eslint config from minimal-deprecated to recommended, and
addresses the resulting violations.


[AB#3014](https://dev.azure.com/fluidframework/235294da-091d-4c29-84fc-cdfc3d90890b/_workitems/edit/3014)

---------

Co-authored-by: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com>
Co-authored-by: Abram Sanderson <Abram.sanderson@gmail.com>
RishhiB pushed a commit to RishhiB/FluidFramework-1 that referenced this pull request Jul 18, 2024
Minor improvements to microsoft#21684, including disabling
[prefer-code-point](https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-code-point.md)
for ordinals, reverting function scope changes, and updating
documentation.

---------

Co-authored-by: Abram Sanderson <Abram.sanderson@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: dds Issues related to distributed data structures base: main PRs targeted against main branch public api change Changes to a public API
9 participants