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

Reduce complexity of src/cli/index.js #2887

Merged
merged 17 commits into from
Apr 10, 2017

Conversation

voxsim
Copy link
Contributor

@voxsim voxsim commented Mar 10, 2017

After working some time on #2810 I thought that maybe I was trying to reduce the complexity of src/cli/index.js from the wrong point of view.

In this pull request I:

Test plan, add test:

  • for not camelised command
  • for not alphabetic command
  • for not unknown command
  • extract npm_config_argv_env_vars correctly if you use 'yarn run test' instead of 'yarn test'
  • display the documentation link correctly if we use an alias
  • display the documentation link correctly if we use a normal command
  • do not display the documentation link correctly if we use have an unknown command

Everything else seems tested properly and this change is less invasive than what i do in #2810.

@bestander let me know if you like and sorry if I spam you a lot :P
Feedbacks from everyone are always welcome :)

Questions:

  • Why we camelcase the command name? And after digging in the function we return null if it has an uppercase letter, otherwise we camelcase the command name. I still don't get what it is the use case O.o

Simon

PS I left some other useful commits from #2810, maybe I will add to this pull request or open others after, in detail:

@voxsim voxsim force-pushed the reduce-complexity-of-tests-index branch 2 times, most recently from 4d8ffe6 to 7272f83 Compare March 10, 2017 16:55
@bestander
Copy link
Member

I'll get back to this in a couple of days, thanks for waiting

@bestander bestander self-assigned this Mar 14, 2017
@bestander bestander self-requested a review March 14, 2017 19:18
@bestander bestander removed their assignment Mar 14, 2017
@voxsim
Copy link
Contributor Author

voxsim commented Mar 14, 2017

I added some comments in the code but i will wait your feedback before pushing anything else ;)

@voxsim
Copy link
Contributor Author

voxsim commented Mar 20, 2017

I opened other two pull requests for what can be saved from #2810 ;)

@voxsim
Copy link
Contributor Author

voxsim commented Apr 1, 2017

@bestander do you have any news on this? :p

@bestander
Copy link
Member

bestander commented Apr 1, 2017 via email

@voxsim
Copy link
Contributor Author

voxsim commented Apr 1, 2017

Hey @bestander , don't worry ;) and thanks for your work!

Copy link
Member

@bestander bestander left a comment

Choose a reason for hiding this comment

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

Great work, @voxsim!
Let's rebase and merge this.

@voxsim
Copy link
Contributor Author

voxsim commented Apr 7, 2017

yeah sure, I need some support from @arcanis for a5a5915. Could you please write me an use case for args = [commandName].concat(getRcArgs(commandName), args);? So I can add a test and see that we won't have any regressions.

@bestander could you please answer about this queston?
Why we camelcase the command name? And after digging in the function we return null if it has an uppercase letter, otherwise we camelcase the command name. I still don't get what it is the use case O.o

I hope to not mistype anything, but sorry I am writing from my phone :p

@voxsim
Copy link
Contributor Author

voxsim commented Apr 7, 2017

ok maybe I get it, sorry @arcanis for bother you ;)

@bestander
Copy link
Member

Why we camelcase the command name? And after digging in the function we return null if it has an uppercase letter, otherwise we camelcase the command name. I still don't get what it is the use case O.o

Just a guess - so that commands could be referred a object properties when needed
like commands.upgradeInteractive.
Although I don't have much background on this issue

@voxsim
Copy link
Contributor Author

voxsim commented Apr 7, 2017

mmh if we do that right now It will not work because it has an uppercase letter. If we don't find any use case maybe we should delete that code and let's wait for complains :p

@arcanis
Copy link
Member

arcanis commented Apr 7, 2017

@voxsim you may have already found it, but the code you quoted is related to this PR, and should be covered by these tests 😃 Let me know if you have specific questions about it

(disregard the "test disabled for now" comment, I've just enabled it but forgot to remove the comment)

@arcanis
Copy link
Member

arcanis commented Apr 7, 2017

Btw, I think there's two reasons the command name is camelCased:

  • It works whether you write yarn foo-bar or yarn fooBar (isn't really useful, but still)
  • The commands are fetched via an import * as commands from './commands' statement, which means that any command name needs to also be valid a Javascript identifier, otherwise the symbol couldn't be exported from within the commands module. So no dash allowed.

@bestander bestander requested a review from arcanis April 7, 2017 18:36
@voxsim
Copy link
Contributor Author

voxsim commented Apr 7, 2017

@arcanis i tried both and they work as expected without camelCase. I always advocate "no code over code that i don't understand", but i don't want to be pushy, I'll let decide you, I understand that some time it's better to be conservative ;)

Btw I finished I should push in a bunch of minutes

@voxsim voxsim force-pushed the reduce-complexity-of-tests-index branch 2 times, most recently from b231c80 to 0dd467c Compare April 7, 2017 21:02
@voxsim voxsim force-pushed the reduce-complexity-of-tests-index branch from 0dd467c to 1e35469 Compare April 9, 2017 09:16
@voxsim
Copy link
Contributor Author

voxsim commented Apr 9, 2017

Ok i finished to rebase everything ;) let me know! For what i see the tests failed for reasons not related to my pull request

@voxsim
Copy link
Contributor Author

voxsim commented Apr 10, 2017

I was wrong and after some digging we use the camelCase function only for 'generate-lock-entry' and 'upgrade-interactive'. Maybe we can achieve the same behaviour without camelCase every command. I will prepare another pull request on this part.

Copy link
Member

@arcanis arcanis left a comment

Choose a reason for hiding this comment

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

Nice work, thanks! :) The only thing I'm a bit concerned with (and that's not related to your PR in itself) is that we seem to be hardcoding a lot of logic that should imo be handled by a dedicated third-party library instead. For example, aliases, camelCasing, this cmd hack, the --help output, extracting the command name from the argv, ...

Maybe in a future PR it could be interesting to look if we can use anything out there that would discard the need to maintain all of this logic. What do you think?

const commandName = camelCase(args.shift());

if (commandName) {
const command = commands[commandName];
Copy link
Member

Choose a reason for hiding this comment

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

We probably should validate every such access with Object.prototype.hasOwnProperty

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we validate here maybe we should validate in src/cli/index.js. What do you think?

Copy link
Member

@arcanis arcanis Apr 10, 2017

Choose a reason for hiding this comment

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

Yep! It will fix this:

❯ [mael-mbp?] yarn git:(0.23-stable) ❯ yarn constructor

yarn constructor v0.23.0
error An unexpected error occurred: "command.run is not a function".
info If you think this is a bug, please open a bug report with the information provided in "/Users/mael/yarn/yarn-error.log".
info Visit https://yarnpkg.com/en/docs/cli/constructor for documentation about this command.

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 a pre-existent error, but i prefer to fix here right now. If I am correctly after this part, we will finish this review.

Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@arcanis done!

continue;
const commandName = camelCase(args.shift());

if (commandName) {
Copy link
Member

Choose a reason for hiding this comment

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

You don't have to check this, since it won't pass the following test (commands[commandName] will be undefined)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have to check this because commandName can be null, sorry I lost this comment!

Copy link
Member

Choose a reason for hiding this comment

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

Yeah ... it seems this camelCase function should rather be called ifCamelCaseNullElseCamelCase 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

YES T_T XD

src/cli/index.js Outdated
let command;
const camelised = camelCase(commandName);
if (camelised) {
command = commands[camelised];
Copy link
Member

Choose a reason for hiding this comment

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

Why not

commandName = camelCase(commandName);

if (!Object.prototype.hasOwnProperty.call(commands, commandName)) {
    args.unshift(commandName);
    commandName = `run`;
}

command = commands[commandName];

?

Copy link
Contributor Author

@voxsim voxsim Apr 10, 2017

Choose a reason for hiding this comment

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

I added a || commandName because camelCase function return null in some cases.

The final version:

commandName = camelCase(commandName) || commandName;

if (!Object.prototype.hasOwnProperty.call(commands, commandName)) {
    args.unshift(commandName);
    commandName = 'run';
}

command = commands[commandName];

Copy link
Contributor Author

@voxsim voxsim Apr 10, 2017

Choose a reason for hiding this comment

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

@arcanis I am sorry but I can't do that :( because if you type yarn custom-script it does something horrible because it camel case custom-script.

Copy link
Member

Choose a reason for hiding this comment

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

Oooooh right :/ damn.

src/cli/index.js Outdated
...startArgs,
// we use this for https://github.com/tj/commander.js/issues/346, otherwise
// it will strip some args that match with any options
'cmd',
Copy link
Member

Choose a reason for hiding this comment

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

Use something more descriptive than cmd, something like this-arg-will-get-stripped-later. It will help the next person to read this hack to understand how it works, and doesn't risk to conflict with any future command.

src/cli/index.js Outdated
}
invariant(command, 'missing command');
// we strip cmd
commander.args.shift();
Copy link
Member

Choose a reason for hiding this comment

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

We probably want to make a console.assert here to make sure that we're shifting the right argument, what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mmh could you please write an example to understand on how you want to use console.assert here?

Copy link
Member

@arcanis arcanis Apr 10, 2017

Choose a reason for hiding this comment

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

console.assert(commander.args.length >= 1);
console.assert(commander.args[0] === 'cmd');
commander.args.shift();

So that if for some reason we would be shifting the wrong argument, the code early crashes instead of doing something else than what the user expected, which might be hard to debug otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

@@ -382,7 +343,7 @@ config.init({
httpsProxy: commander.httpsProxy,
networkConcurrency: commander.networkConcurrency,
nonInteractive: commander.nonInteractive,
commandName,
commandName: commandName === 'run' ? commander.args[0] : commandName,
Copy link
Member

Choose a reason for hiding this comment

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

Not sure we can do this; yarn version and yarn run version would both end up with a version commandName, which is semantically wrong because they have different behaviors. If we want to support user-defined commands here, then it should be in a separate field.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok this was a pre-existent error, can we open another pull request on this?

Copy link
Member

Choose a reason for hiding this comment

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

Yep, you're right. Let's keep the current behavior

Copy link
Contributor Author

@voxsim voxsim Apr 10, 2017

Choose a reason for hiding this comment

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

This evening I have to remember to open at least one issue, otherwise I will forget everything :)

continue;
const commandName = camelCase(args.shift());

if (commandName) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I need to check what happened when i type yarn unknown-command --help. It should ignore everything and display standard help. Maybe this behaviour is wrong

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK I was right. In detail: yarn unknown --help

before:

  Usage: yarn [command] [flags]

  Options:

    -h, --help                      output usage information
    -V, --version                   output the version number
    --verbose                       output verbose messages on internal operations
    --offline                       trigger an error if any required dependencies are not available in local cache
    --prefer-offline                use network only if dependencies are not available in local cache
    --strict-semver
    --json
    --ignore-scripts                don't run lifecycle scripts
    --har                           save HAR output of network traffic
    --ignore-platform               ignore platform checks
    --ignore-engines                ignore engines check
    --ignore-optional               ignore optional dependencies
    --force                         ignore all caches
    --no-bin-links                  don't generate bin links when setting up packages
    --flat                          only allow one version of a package
    --prod, --production [prod]
    --no-lockfile                   don't read or generate a lockfile
    --pure-lockfile                 don't generate a lockfile
    --frozen-lockfile               don't generate a lockfile and fail if an update is needed
    --link-duplicates               create hardlinks to the repeated modules in node_modules
    --global-folder <path>
    --modules-folder <path>         rather than installing modules into the node_modules folder relative to the cwd, output them here
    --cache-folder <path>           specify a custom folder to store the yarn cache
    --mutex <type>[:specifier]      use a mutex to ensure only one yarn instance is executing
    --no-emoji                      disable emoji in output
    --proxy <host>
    --https-proxy <host>
    --no-progress                   disable progress bar
    --network-concurrency <number>  maximum number of concurrent network requests

after pr:

    Usage: yarn [command] [flags]

  Options:

    -h, --help                      output usage information
    -V, --version                   output the version number
    --verbose                       output verbose messages on internal operations
    --offline                       trigger an error if any required dependencies are not available in local cache
    --prefer-offline                use network only if dependencies are not available in local cache
    --strict-semver
    --json
    --ignore-scripts                don't run lifecycle scripts
    --har                           save HAR output of network traffic
    --ignore-platform               ignore platform checks
    --ignore-engines                ignore engines check
    --ignore-optional               ignore optional dependencies
    --force                         install and build packages even if they were built before, overwrite lockfile
    --skip-integrity-check          run install without checking if node_modules is installed
    --no-bin-links                  don't generate bin links when setting up packages
    --flat                          only allow one version of a package
    --prod, --production [prod]
    --no-lockfile                   don't read or generate a lockfile
    --pure-lockfile                 don't generate a lockfile
    --frozen-lockfile               don't generate a lockfile and fail if an update is needed
    --link-duplicates               create hardlinks to the repeated modules in node_modules
    --global-folder <path>
    --modules-folder <path>         rather than installing modules into the node_modules folder relative to the cwd, output them here
    --cache-folder <path>           specify a custom folder to store the yarn cache
    --mutex <type>[:specifier]      use a mutex to ensure only one yarn instance is executing
    --no-emoji                      disable emoji in output
    --proxy <host>
    --https-proxy <host>
    --no-progress                   disable progress bar
    --network-concurrency <number>  maximum number of concurrent network requests
    --non-interactive               do not show interactive prompts

  Commands:

    - access
    - add
    - bin
    - cache
    - check
    - clean
    - config
    - generate-lock-entry
    - global
    - help
    - import
    - info
    - init
    - install
    - licenses
    - link
    - list
    - login
    - logout
    - outdated
    - owner
    - pack
    - publish
    - remove
    - run
    - tag
    - team
    - unlink
    - upgrade
    - upgrade-interactive
    - version
    - versions
    - why

  Run `yarn help COMMAND` for more information on specific commands.
  Visit https://yarnpkg.com/en/docs/cli/ to learn more about Yarn.

Copy link
Member

Choose a reason for hiding this comment

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

Good idea! I'd probably put them on a single line, comma-separate, restricted to 80 columns. Otherwise they take a lot of vertical space.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean the commands part? I can do that if you confirm

Copy link
Member

Choose a reason for hiding this comment

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

Yep!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can't overwrite the commands part without doing other horrible things for what I understand because it is a feature of commander.js -.-. In detail the function help.

Copy link
Member

Choose a reason for hiding this comment

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

Actually I think it's done userland, more precisely here. That being said, I've checked locally and it seems that this display (one command each line) was already printed this way in recent Yarn releases, so let's keep it this way. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right! As you wish :)

src/cli/index.js Outdated
let command;
const camelised = camelCase(commandName);
if (camelised) {
command = commands[camelised];
Copy link
Contributor Author

@voxsim voxsim Apr 10, 2017

Choose a reason for hiding this comment

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

I added a || commandName because camelCase function return null in some cases.

The final version:

commandName = camelCase(commandName) || commandName;

if (!Object.prototype.hasOwnProperty.call(commands, commandName)) {
    args.unshift(commandName);
    commandName = 'run';
}

command = commands[commandName];

@@ -382,7 +343,7 @@ config.init({
httpsProxy: commander.httpsProxy,
networkConcurrency: commander.networkConcurrency,
nonInteractive: commander.nonInteractive,
commandName,
commandName: commandName === 'run' ? commander.args[0] : commandName,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok this was a pre-existent error, can we open another pull request on this?

src/cli/index.js Outdated
}
invariant(command, 'missing command');
// we strip cmd
commander.args.shift();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mmh could you please write an example to understand on how you want to use console.assert here?

@voxsim
Copy link
Contributor Author

voxsim commented Apr 10, 2017

Maybe in a future PR it could be interesting to look if we can use anything out there that would discard the need to maintain all of this logic. What do you think?

@arcanis I think you are totally right about that ;)

If you see some something weird about reviews, I am sorry but I forgot to submit many days ago O.o

@voxsim voxsim force-pushed the reduce-complexity-of-tests-index branch from 1e35469 to 4d930e9 Compare April 10, 2017 13:08
@arcanis arcanis self-assigned this Apr 10, 2017
@@ -70,7 +70,7 @@ export function getRcArgs(command: string): Array<string> {

let result = rcArgsCache['*'] || [];

if (command !== '*') {
if (command !== '*' && Object.prototype.hasOwnProperty.call(rcArgsCache, command)) {
Copy link
Member

Choose a reason for hiding this comment

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

Ah! You got me :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

:D

Copy link
Member

@arcanis arcanis left a comment

Choose a reason for hiding this comment

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

Great job 👍 I'll just wait to see what Travis has to say, but it's good for me

@arcanis arcanis merged commit 45af656 into yarnpkg:master Apr 10, 2017
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.

3 participants