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

morebits: remove Morebits.queryString #725

Closed
wants to merge 2 commits into from

Conversation

siddharthvp
Copy link
Member

Morebits.queryString is redundant to mw.util.getParamValue(), mw.util.getUrl() and jQuery.param(). All existing usages are easily replaced.
Specifically,

  • Morebits.queryString.get() and Morebits.queryString.exists() are redundant to mw.util.getParamValue()
  • Morebits.queryString.create() is redundant to mw.util.getUrl() and jQuery.param()

While queryString can be also used for constructing query string objects and getting/setting param values in them, this functionality is not used anywhere, nor are there any reasonable potential usecases.

This is untested, for now.

@Amorymeltzer Amorymeltzer added the Module: morebits The morebits.js library label Oct 15, 2019
@siddharthvp
Copy link
Member Author

It's also worth noting that the whole Morebits.queryString class is redundant to the native JS URLSearchParams interface (not available in IE, though).

@Amorymeltzer Amorymeltzer self-assigned this Nov 3, 2019
@Amorymeltzer
Copy link
Collaborator

Haven't taken a look at the code yet, but wholeheartedly agree with this. I think it was the fluff stuff I was doing earlier this year that made me really wonder why we still bothered with queryString.

.replace(/^(.*?)(&token=[^&]*)(.*)/, '$1$3$2') // shunt token from middle of query string to the end
.replace(/^(token=[^&]*)&(.*)/, '$2&$1'); // shunt token from beginning of query string to the end
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I wrote this initially as

	if (this.query.token) {
		var token = this.query.token;
		delete this.query.token;
		this.query.token = token;
	}

followed by $.param(this.query.token), but the delete operator is purported to cause performance impacts in V8, and isn't used anywhere else in Twinkle. Anyway, the two lines of ugly regex is much better than the 25 lines of ugly code that was there originally in Morebits.queryString.create!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Much nicer! The original is certainly over-complicated, but I think casting into an array is indeed the generic way to do it (sans jQuery and morebits of course). Either way, this should work for our specific purposes, so w00t!

Could we make it even tighter, though, by relying on the fact that tokens end in +\? Would something like the following be sufficient?

queryString = $.param(this.query).replace(/^(.*?)(token=[^%]+%2B%5C)&(.*)/, '$1$3&$2')

It:

  • matches until %
  • only matches if something is present (not sure what happens if there's an empty token parameter isn't last, but that shouldn't happen)
  • removes the trailing & and places one before the terminal token:
    • if token is first, the ? remains for the new first parameter
    • if token is in the middle, token's & remains for the upcoming parameter
    • if token is last, there won't be a terminal & to match and there's nothing to do anyway!

Copy link
Member Author

Choose a reason for hiding this comment

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

Great work in condensing it down to a single regex replace!

(token=[^%]+%2B%5C)

not so sure whether this is an improvement. While a nice idea, it seems better to me to leave it as

(token=[^&]*)

$.param(this.query) encodes the URI components so & can't occur as part of the token itself. So encountering an & means that the token part has ended. So we aren't really tightening up anything right?

Copy link
Member Author

@siddharthvp siddharthvp Jan 10, 2020

Choose a reason for hiding this comment

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

NOTE: The code here doesn't account for the other features of Morebits.queryString.create - omitting parameters with value undefined and converting array parameters to |-delimited strings.

This needs to be resolved before merge. Now done.

Copy link
Collaborator

Choose a reason for hiding this comment

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

So that'd be queryString = $.param(this.query).replace(/^(.*?)(token=[^&]+)&(.*)/, '$1$3&$2'), right? (+ rather than *)

Copy link
Member Author

Choose a reason for hiding this comment

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

meh, using * means an empty token= also gets moved to the end, which is better right?

@Amorymeltzer Amorymeltzer added this to the January 2020 update milestone Nov 12, 2019
@Amorymeltzer
Copy link
Collaborator

Amorymeltzer commented Dec 19, 2019

Not sure I'll have time before the holidays, but just some history as I start clearing the way:

@Amorymeltzer
Copy link
Collaborator

Is it worth attempting to deprecate these first? Maybe swapping in their equivalents would be easiest too start, and catch any edge cases. bI haven't yet checked usages elsewhere, but if there are none as you say @siddharthvp, then maybe that's overkill/assigning too much importance.

@siddharthvp
Copy link
Member Author

siddharthvp commented Dec 19, 2019

18 usages in userspace:

var query = new Morebits.queryString( anchors[i].href.split('?',2)[1].toString() );
var link = query.get('title');

to

var link = anchors[i].href.match(/\btitle=([^&]*)/)[1]; 

if at all it worked before.

I don't think there's any usage worth giving deprecation warnings for.

query.gapprefix = Morebits.queryString.exists('from') ? Morebits.string.toUpperCaseFirstChar(Morebits.queryString.get('from').replace('+', ' ')) :
Morebits.string.toUpperCaseFirstChar($('input[name=prefix]').val());
query.gapnamespace = mw.util.getParamValue('namespace') || $('select[name=namespace]').val();
query.gapprefix = mw.util.getParamValue('prefix') || $('input[name=prefix]').val();
Copy link
Collaborator

@Amorymeltzer Amorymeltzer Jan 6, 2020

Choose a reason for hiding this comment

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

Wow! from is used to paginate, while prefix is the actual prefix. That is, prefix defines the list (e.g. prefix=A) while from defines what is shown on the user's screen (e.g. prefix=A&from=Amory_Lovins). batchprotect has been busted on non-first-page results for a decade, very lame!

Copy link
Collaborator

@Amorymeltzer Amorymeltzer left a comment

Choose a reason for hiding this comment

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

Looks pretty good to me, @siddharthvp! As above, I think this is good to do; we're not attempting to support old MW versions, and with with jQuery and mw.util a lot of this is unnecessary. Maybe there's places we don't want to remove things from the morebits library, but I'm fine with queryString going. cc @MusikAnimal, @atlight, and @azatoth in case any of you feel strongly or want to review.

The only thing I do think might be worth holding onto is the Morebits.queryString function itself, or at least something like it. I'm not familiar with an easy way using jQuery or mw.util to build an object with each parameter (e.g. new Morebits.queryString(string).params), am I wrong on that? It's not used by Twinkle, but that feels useful and fairly low overhead. See below

.replace(/^(.*?)(&token=[^&]*)(.*)/, '$1$3$2') // shunt token from middle of query string to the end
.replace(/^(token=[^&]*)&(.*)/, '$2&$1'); // shunt token from beginning of query string to the end
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Much nicer! The original is certainly over-complicated, but I think casting into an array is indeed the generic way to do it (sans jQuery and morebits of course). Either way, this should work for our specific purposes, so w00t!

Could we make it even tighter, though, by relying on the fact that tokens end in +\? Would something like the following be sufficient?

queryString = $.param(this.query).replace(/^(.*?)(token=[^%]+%2B%5C)&(.*)/, '$1$3&$2')

It:

  • matches until %
  • only matches if something is present (not sure what happens if there's an empty token parameter isn't last, but that shouldn't happen)
  • removes the trailing & and places one before the terminal token:
    • if token is first, the ? remains for the new first parameter
    • if token is in the middle, token's & remains for the upcoming parameter
    • if token is last, there won't be a terminal & to match and there's nothing to do anyway!

morebits.js Show resolved Hide resolved
@siddharthvp
Copy link
Member Author

The only thing I do think might be worth holding onto is the Morebits.queryString function itself, or at least something like it. I'm not familiar with an easy way using jQuery or mw.util to build an object with each parameter (e.g. new Morebits.queryString(string).params), am I wrong on that? It's not used by Twinkle, but that feels useful and fairly low overhead.

new mw.Uri(string).query does do that, and the mw.Uri dependency is pretty cheap. The only trouble with it is that it needs a full URL and won't accept just a querystring.

Do you still think that this is needed @Amorymeltzer?

@Amorymeltzer
Copy link
Collaborator

new mw.Uri(string).query does do that, and the mw.Uri dependency is pretty cheap.

💯 TIL! And what dependency, is it not in core?

The only trouble with it is that it needs a full URL and won't accept just a querystring.

That's not an issue for me. I was honestly more interested in the default behavior using the current location string, as I thought it could be handy to be able to easily iterate on each of the parameters. It should only make a difference if trying to parse a pseudo link on the page that had the parameter string but not the full url (i.e. someone wrote "You can just append ?query=action&titles=Macbeth to the en.wikipedia.org but use /w/index.php instead of /wiki/" which is, uh, not a usecase. Otherwise, it's a full url, the default from location.href, an object we already own (making it pointless), or we can just toss it behind mw.util.getUrl`. All easy.

Do you still think that this is needed @Amorymeltzer?

Nope! 👍

@Amorymeltzer
Copy link
Collaborator

@siddharthvp With https://github.com/azatoth/twinkle/compare/dde1faa4143ac42ce8b788034243186e89d1ae1a..5bd60477a2f829cedeaeaae741d628dce77cbc3b am I right that you're done working on this, and I can give it a final review? Looking forward to it!

@siddharthvp
Copy link
Member Author

@Amorymeltzer yeah 👍 sorry if was being confusing above.

Copy link
Collaborator

@Amorymeltzer Amorymeltzer left a comment

Choose a reason for hiding this comment

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

I worked out all the merge conflicts locally, so I think this is ready to go with one minor change! Whoo!

morebits.js Outdated Show resolved Hide resolved
Copy link
Collaborator

@Amorymeltzer Amorymeltzer left a comment

Choose a reason for hiding this comment

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

One more bug that needs fixing. The this.query issue I'm fairly confident about, but wouldn't mind your confirmation/testing that this is the best way to deal with this for now.

morebits.js Outdated
} else if (val !== undefined) {
return encodeURIComponent(i) + '=' + encodeURIComponent(val);
}
}).join('&').replace(/^(.*?)(token=[^&]*)&(.*)/, '$1$3&$2');
Copy link
Collaborator

@Amorymeltzer Amorymeltzer Jan 23, 2020

Choose a reason for hiding this comment

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

Okay there's actually another issue here @siddharthvp — for queries with intoken (e.g. before editing) this leaves the in behind for another parameter. Not good. We're not going to overhaul the token system right now, so for now I think this should do it:

Suggested change
}).join('&').replace(/^(.*?)(token=[^&]*)&(.*)/, '$1$3&$2');
}).join('&').replace(/^(.*?)((?:in)?token=[^&]*)&(.*)/, '$1$3&$2');

But I'm not 100% it's good enough.

Copy link
Collaborator

@Amorymeltzer Amorymeltzer Jan 23, 2020

Choose a reason for hiding this comment

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

Actually, I think that's technically backwards; we don't want to move intoken (well, I suppose it doesn't really matter) so it'd be better to do something like either:

Suggested change
}).join('&').replace(/^(.*?)(token=[^&]*)&(.*)/, '$1$3&$2');
}).join('&').replace(/^(.*?)[&\?]?(token=[^&]*)&(.*)/, '$1$3&$2');

or

Suggested change
}).join('&').replace(/^(.*?)(token=[^&]*)&(.*)/, '$1$3&$2');
}).join('&').replace(/^(.*?)([^in]token=[^&]*)&(.*)/, '$1$3&$2');

Copy link
Member Author

Choose a reason for hiding this comment

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

How about using \b ? It has zero-length match, so I think that should be ideal.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Mmm, yes, I think that's probably right/best/parsimonious

Copy link
Member Author

Choose a reason for hiding this comment

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

appears to work fine, pushed.

Copy link
Collaborator

@Amorymeltzer Amorymeltzer Jan 23, 2020

Choose a reason for hiding this comment

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

Is there a difference between \b(token... and (\btoken...? Off hand I'm not thinking there should be 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.

\b has a zero-length match, so no.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah - I was wondering more about ZWSP and exotics like that, I curious how they "counted." Turns out they're \S and `\W!

Morebits.queryString is redundant to mw.util.getParamValue(), mw.util.getUrl() and jQuery.param(). All existing usages are easily replaced.
Specifically,
- Morebits.queryString.get() and Morebits.queryString.exists() are redundant to mw.util.getParamValue()
- Morebits.queryString.create() is redundant to mw.util.getUrl() and jQuery.param()

While queryString can be also used for constructing query string objects and getting/setting param values in them, this functionality is not used anywhere,
nor are there any reasonable potential usecases.
PrefixIndex URLs use `prefix` parameter, not a `from` parameter.
Also, mw.util.getParamValue takes care of converting `+` to space, (though queryString also does that since f0866be; before that commit this was a bug)
Also, use of Morebits.toUpperCaseFirstChar is unnecessary, that is taken care of by the API.
@Amorymeltzer
Copy link
Collaborator

I'll do some more testing later today, but I think this is good to go!

Copy link
Collaborator

@Amorymeltzer Amorymeltzer left a comment

Choose a reason for hiding this comment

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

Will merge locally, but 🎉

Amorymeltzer added a commit that referenced this pull request Jan 23, 2020
Morebits.queryString is redundant to mw.util.getParamValue(), mw.util.getUrl() and jQuery.param(). All existing usages are easily replaced.
Specifically,
- Morebits.queryString.get() and Morebits.queryString.exists() are redundant to mw.util.getParamValue()
- Morebits.queryString.create() is redundant to mw.util.getUrl() and jQuery.param()

While queryString can be also used for constructing query string objects and getting/setting param values in them, this functionality is not used anywhere,
nor are there any reasonable potential usecases.

Co-authored-by: Siddharth VP <siddharthvp@gmail.com>
Co-authored-by: Amory Meltzer <Amorymeltzer@gmail.com>
@Amorymeltzer
Copy link
Collaborator

Merged as 37d6b67 and bf1919f

Amorymeltzer added a commit to Amorymeltzer/twinkle that referenced this pull request Apr 7, 2020
Fixes wikimedia-gadgets#913, same basic issue as wikimedia-gadgets#627/wikimedia-gadgets#628: if a user has "Do not show page content below diffs" selected in Special:Preferences, wgRevisionId is set to 0 on diffs.  While wikimedia-gadgets#628 took care of that for the links on diff pages themselves, trying to rollback from a contribs page was left unfixed.  Those links load the diff page then `auto`matically revert, so will run into the same issue.  This essentialy reverts back to the check before 3e5ec5b (wikimedia-gadgets#561), albeit by using `mw.util.getParamValue` rather than the no-longer-existing `Morebits.queryString.get` (wikimedia-gadgets#725).
Amorymeltzer added a commit to Amorymeltzer/twinkle that referenced this pull request Jun 28, 2020
Lost in between adding JSON support (19435c4/wikimedia-gadgets#493) and removing `queryString` (37d6b67/wikimedia-gadgets#725)
wiki-ST47 pushed a commit to wiki-ST47/twinkle that referenced this pull request Sep 2, 2020
Fixes wikimedia-gadgets#913, same basic issue as wikimedia-gadgets#627/wikimedia-gadgets#628: if a user has "Do not show page content below diffs" selected in Special:Preferences, wgRevisionId is set to 0 on diffs.  While wikimedia-gadgets#628 took care of that for the links on diff pages themselves, trying to rollback from a contribs page was left unfixed.  Those links load the diff page then `auto`matically revert, so will run into the same issue.  This essentialy reverts back to the check before 3e5ec5b (wikimedia-gadgets#561), albeit by using `mw.util.getParamValue` rather than the no-longer-existing `Morebits.queryString.get` (wikimedia-gadgets#725).
wiki-ST47 pushed a commit to wiki-ST47/twinkle that referenced this pull request Sep 2, 2020
Lost in between adding JSON support (19435c4/wikimedia-gadgets#493) and removing `queryString` (37d6b67/wikimedia-gadgets#725)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Module: batch The three batch modules and deprod Module: morebits The morebits.js library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants