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

first draft of operator_position functionality #735

Merged
merged 1 commit into from
Feb 3, 2016

Conversation

olsonpm
Copy link
Contributor

@olsonpm olsonpm commented Jul 16, 2015

  • Added verification of operator_position option. Throws error if
    invalid.
  • Possible operator_position options are:
    1. undefined (use default behavior)
    2. 'before_newline'
    3. 'after_newline'
    4. 'preserve_newline'
    5. 'remove_newline'
  • Moved some code around in handle_operator in order to separate
    conflicting logic when handling operators.
  • Modified allow_wrap_or_preserved_newline to handle operator
    preserved newlines as well.
  • Fixed a few boolean variable assignments within if statements.
    Though not always the case, the expression within the if can
    be assigned directly to the variable
  • Added -> built tests
  • Within the tests, added an 'inputlib.js' file which holds common
    input arrays. This was necessary due to the matrices not working
    cleanly with default behavior.
  • It is worth noting that although the python test file was modified,
    this is a result of the built test and not actually providing
    a python port for this option (yet).

@olsonpm
Copy link
Contributor Author

olsonpm commented Jul 16, 2015

So instead of figuring out the redundant indentation logic and attempt to condense the tests via matrices, I just made it easy on myself and separated the tests. I added tests for each combination of operator_position and preserve_newlines, meaning there should be 10 new sections of tests total.

Also, as noted in the commit message - I'm expecting the python tests to fail until we verify the javascript functionality. I can write the logic for python afterward.

@olsonpm olsonpm mentioned this pull request Jul 16, 2015
@bitwiseman
Copy link
Member

Let me start by saying - great job. Thanks for putting the full effort into this and I approve of everything you mentioned in your description, including not using matrix style for testing.

The size of the change is a little daunting - though I get that the test portions are largely boilerplate.

I'll take a look at the overall structure of the product code changes so that you are unblocked to do the python porting. Then I'll look at the detailed tests to make sure everything is coming out the way it should.

Thanks again!

opt.operator_position = options.operator_position;

// throws error if invalid value was passed in
verifyOperatorPosition(opt.operator_position);
Copy link
Member

Choose a reason for hiding this comment

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

Maybe change this to sanitizeOperatorPosition and have the function return the value to be assigned (replacing undefined with the default).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you clarify "replacing undefined with the default"? I'm expecting undefined to be the default value.

change this to sanitizeOperatorPosition and have the function return the value to be assigned

Will do.

@olsonpm
Copy link
Contributor Author

olsonpm commented Jul 20, 2015

Yeah I thought the testing might have been overkill. It was more my method of development than me testing the end result (start by ensuring all the operators worked properly -> the special colon case -> then a catch all).

Feel free to let me know what you want slimmed down and I'll do it. However I will wait to hear the full product-changes feedback before working on it further.

Finally, this is my first sizable open source contribution - so don't hesitate to critique anything. Normally I just fork and modify for my personal needs.


function verifyOperatorPosition(opPosition) {
var validPositionValues = [undefined];
Object.keys(OPERATOR_POSITION).forEach(function(pos) {
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 if this is compatible with older browsers (IE is a pain).

Here's a polyfill to work around it:
http://tokenposts.blogspot.com.au/2012/04/javascript-objectkeys-browser.html

But, you might have to do this instead:

opPosition = opPosition || OPERATOR_POSITION. before_newline;
if (!OPERATOR_POSITION[opPosition]) {
    throw new Error();
}
return opPosition;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ha - I had no idea IE had trouble with that. I'll implement it without the polyfill.

@bitwiseman
Copy link
Member

Okay, I'm done with my initial pass.
There's the design issue of needing to make the preserve_newlines <--> operator_position interaction consistent, and some code tweaks, but rather minimal.

Thanks again!

@olsonpm
Copy link
Contributor Author

olsonpm commented Jul 21, 2015

Sounds good. I'll have time this weekend to take a look and make changes.

@olsonpm
Copy link
Contributor Author

olsonpm commented Jul 24, 2015

Wow - I think I see my misunderstanding. The operator_position shouldn't do anything if preserve_newlines is false. It's only positioning the operators that are placed next to a newline that the user wants preserved. This is why you're mentioning the default behavior 'undefined' should equal 'before_newline'.

I took the option names far too literally. I'll work on this now and have an update tonight hopefully.

Edit: Ugh - looking back at your comment, the section is different from the <before_newline>. So I guess I still need clarification on the default being before_newline since I assume you don't want to change the default behavior for everyone's beautified code upon updating it.

And I apologize for the number of updates to this thread. I'll leave it alone for now.

@bitwiseman
Copy link
Member

@olsonpm - I've been away. It looks like you've been away too. You wan to take another swing at this?

@olsonpm
Copy link
Contributor Author

olsonpm commented Nov 10, 2015

Not a problem.

I think our ideas differ on how to handle default behavior (i.e. no option specified)

My understanding is that it should behave as it did before

It seems from your comment above that before_newline should be the default behavior. Note this will be a breaking change.

@bitwiseman
Copy link
Member

Hm, It's probably an error in understanding on my part.
I thought OPERATOR_POSITION.before_newline is the same as the current behavior. I'll go look again.

@olsonpm
Copy link
Contributor Author

olsonpm commented Nov 10, 2015

Referring back to your sample code you can see a few subtle differences:

// Test code
var d = 1;
if (a === b 
    && c) {
    d = ((c * everything / something_else)  
            - a) %
        b;
    e 
        += d;

} else if (!(complex && simple) ||
        (emotion && emotion.strong && emotion.name === 'happy')) {
    cryTearsOfJoy(many ||
        anOcean ||
        aRiver);
}

// Output: actual current 
var d = 1;
if (a === b && c) {
    d = ((c * everything / something_else) - a) %
        b;
    e
        += d;

} else if (!(complex && simple) ||
    (emotion && emotion.strong && emotion.name === 'happy')) {
    cryTearsOfJoy(many ||
        anOcean ||
        aRiver);
}

// Output: operator_positioning = before_newline
var d = 1;
if (a === b &&
    c) {
    d = ((c * everything / something_else) -
            a) %
        b;
    e +=
        d;

} else if (!(complex && simple) ||
        (emotion && emotion.strong && emotion.name === 'happy')) {
    cryTearsOfJoy(many ||
        anOcean ||
        aRiver);
}

@bitwiseman
Copy link
Member

Ah, I see where we're out of sync. In all the complexity, I didn't cover the base case.

  opts.preserve_newlines = false;
  opts.operator_position = undefined;
  bt('var res = a + b - c / d * e % f');   // unchanged
  bt('var res = a + b -\n' +
      '    c / d * e % f',
      'var res = a + b - c / d * e % f');  // newline removed 
  bt('var res = a + b\n' +
      '    - c / d * e % f',
      'var res = a + b - c / d * e % f');  // newline removed 

  opts.operator_position = 'before_newline';
  bt('var res = a + b - c / d * e % f');   // unchanged
  bt('var res = a + b -\n' +
      '    c / d * e % f',
      'var res = a + b - c / d * e % f');  // newline removed 
  bt('var res = a + b\n' +
      '    - c / d * e % f',
      'var res = a + b - c / d * e % f');  // newline removed 

  opts.operator_position = 'after_newline';
  bt('var res = a + b - c / d * e % f');   // unchanged
  bt('var res = a + b -\n' +
      '    c / d * e % f',
      'var res = a + b - c / d * e % f');  // newline removed 
  bt('var res = a + b\n' +
      '    - c / d * e % f',
      'var res = a + b - c / d * e % f');  // newline removed 

  opts.operator_position = 'preserve_newline';
  bt('var res = a + b - c / d * e % f');   // unchanged
  bt('var res = a + b -\n' +
      '    c / d * e % f',
      'var res = a + b - c / d * e % f');  // newline removed 
  bt('var res = a + b\n' +
      '    - c / d * e % f',
      'var res = a + b - c / d * e % f');  // newline removed 

  opts.preserve_newlines = true;
  opts.operator_position = undefined;
  bt('var res = a + b - c / d * e % f');   // unchanged
  bt('var res = a + b -\n' +
      '    c / d * e % f');  // unchanged 

 // NOTE: Below is different from current behavior
  // Current behavior removes the newline, this is a breaking change, but this seems more consistent and correct.
  // However, if we want to be non-breaking, having the newline be removed here is also acceptable.  
  bt('var res = a + b\n' +
      '    - c / d * e % f',
      'var res = a + b -\n' +
      '    c / d * e % f',);  // newline preserved, operator moved to before the newline

  opts.operator_position = 'before_newline';
  bt('var res = a + b - c / d * e % f');   // unchanged
  bt('var res = a + b -\n' +
      '    c / d * e % f');  // unchanged 
  bt('var res = a + b\n' +
      '    - c / d * e % f',
      'var res = a + b -\n' +
      '    c / d * e % f',);  // newline preserved, operator moved to before the newline

  opts.operator_position = 'after_newline';
  bt('var res = a + b - c / d * e % f');   // unchanged
  bt('var res = a + b -\n' +
      '    c / d * e % f',
      'var res = a + b\n' +
      '    - c / d * e % f',);  // newline preserved, operator moved to after the newline
  bt('var res = a + b\n' +
      '    - c / d * e % f');  // unchanged

  opts.operator_position = 'preserve_newline';
  bt('var res = a + b - c / d * e % f');   // unchanged
  bt('var res = a + b -\n' +
      '    c / d * e % f');  // unchanged
  bt('var res = a + b\n' +
      '    - c / d * e % f');  // unchanged

What you have forces newlines after all operators even when there were none. What we want is allow newlines in a specific position for operators and optionally move the newline if the code has it in on the wrong side.

I'm really sorry for not catching this earlier. Totally my bad.

@olsonpm
Copy link
Contributor Author

olsonpm commented Nov 10, 2015

No problem - I added to the confusion by initially coding it that way. I still have my branch so I can take a look at it this weekend and get back with an up-to-date PR.

Edit: And just to confirm - do we want the breaking change or not? I don't mind coding it either way, I just had assumed we would avoid it.

@bitwiseman
Copy link
Member

Let's do the breaking change I showed above.

It is different, but I'd argue that it is also more correct. Right now the beautifier punishes people for putting newlines on the wrong side of operators - it looses their intended formatting. The "break" is that is it will try to preserve their formatting. I'm okay with that.

Thanks!

@olsonpm
Copy link
Contributor Author

olsonpm commented Nov 13, 2015

I'm on board, and definitely agree it's more correct.

@olsonpm
Copy link
Contributor Author

olsonpm commented Nov 16, 2015

Just an fyi - I didn't find time this weekend unfortunately. I am motivated to get this done though and will give an update by mid next week

@bitwiseman
Copy link
Member

No problem. I (obviously) know how it goes. Thanks!

@olsonpm olsonpm force-pushed the add-operator-position branch from f600f26 to fb4c5f9 Compare December 14, 2015 00:30
@olsonpm
Copy link
Contributor Author

olsonpm commented Dec 14, 2015

@bitwiseman - Took a good stab at this tonight, ran into a case I'm unsure about:

When operator_position is set to remove_newline What should the following resolve to?

var a = b

=== c;

The answer depends on whether you want preserve_newlines or operator_position to take precedence. This if statement would need to check for opt.operator_position !== OPERATOR_POSITION.remove_newline if we want the newlines to collapse in the above case.

I'll bump this in two weeks if I don't hear back.

@bitwiseman
Copy link
Member

This is why I didn't include remove_newline as an option in my list above. 😄

I figured preserve_newlines would take precedence: if set to false existing newlines are ignored, if set to true the behavior can be customized.

Unless you feel we really need remove_newline, I'd drop it. It seems like something most people would not want - "keep newlines in general, but remove them when they occur before or after an operator"?

@olsonpm
Copy link
Contributor Author

olsonpm commented Dec 24, 2015

Ah - that's what I get for skimming. Thanks much for the clarification.

@olsonpm
Copy link
Contributor Author

olsonpm commented Dec 25, 2015

@bitwiseman - I feel pretty good about this second draft. Ensuring the operator position logic only applies when preserve_newlines is true simplifies everything, especially tests.

Honestly I was very surprised our breaking change didn't cause a bunch of tests to break. I was expecting to have to go through and fix a ton of them.

By the way, instead of looking at the diff between the first -> second draft, it may make more sense to diff master -> second draft. My branch should be rebased to the current master.

@olsonpm
Copy link
Contributor Author

olsonpm commented Feb 3, 2016

*Rebasing and cleaning up the commit message now.

If there's anything about my commit message you want changed, it would be easiest if you just create another pull request and modify it yourself to save back-and-forth. I'm not concerned about having my name on anything.

@bitwiseman
Copy link
Member

I'm not concerned about the message, except put an empty line below the title.

Title

Descrption
Descrption
Descrption

@olsonpm olsonpm force-pushed the add-operator-position branch from 0ee8f3e to 007c57d Compare February 3, 2016 19:00
@olsonpm
Copy link
Contributor Author

olsonpm commented Feb 3, 2016

Alright - I looked at the readme and couldn't figure out why some cli options were left out so I'll leave it be for now.

 - Add sanitization of operator_position option.  Throws error if invalid.
 - Possible operator_position options are:
    1) 'before-newline' (default)
    2) 'after-newline'
    3) 'preserve-newline'
 - Move some code around in handle_operator in order to separate
    conflicting logic now that operator newlines are accounted for.
 - Modify allow_wrap_or_preserved_newline to handle operator
    preserved newlines as well.
 - Add -> build tests
 - Within the tests, add an 'inputlib.js' file holding common
    input arrays.  This was necessary due to the matrices not working
    cleanly with default behavior.

Changes not directly relevant to operator_position
 - Add Object.values polyfill
 - Ignore intellijidea project file
 - Fix a few boolean variable assignments within if statements.
    Though not always the case, the expression within the if can
    be assigned directly to the variable
@olsonpm olsonpm force-pushed the add-operator-position branch from 007c57d to d83308d Compare February 3, 2016 19:05
@bitwiseman
Copy link
Member

The readme being out of date is totally possible. But unless it applies to this change getting it up to date is not needed.

@bitwiseman
Copy link
Member

Ah I see I didn't update the help message in python. Meh, I'll get that in a minute.

@olsonpm
Copy link
Contributor Author

olsonpm commented Feb 3, 2016

Gotcha - and yeah I just thought updating the readme might make more sense in a separate commit to master.

bitwiseman added a commit that referenced this pull request Feb 3, 2016
first draft of operator_position functionality
@bitwiseman bitwiseman merged commit 5395e0d into beautifier:master Feb 3, 2016
@olsonpm olsonpm deleted the add-operator-position branch February 3, 2016 19:33
@bitwiseman
Copy link
Member

@olsonpm - thanks again for all your hard work on this. 👍

@nerdpad
Copy link

nerdpad commented Mar 19, 2016

@bitwiseman what is the jsbeautifyrc option for this feature?

I added "operator_position": "preserve-newline" to my rc, still it inlines ternary operators.

@olsonpm
Copy link
Contributor Author

olsonpm commented Mar 19, 2016

@nerdpad - what version are you using?

@nerdpad
Copy link

nerdpad commented Mar 21, 2016

@olsonpm I am using v1.6.2. Here is the output that I am getting:

Source:

const result = true
    ? true
    : false;

After format:

const result = true ? true : false;

.jsbeautifyrc

{
    "indent_size": 4,
    "indent_char": " ",
    "eol": "\n",
    "indent_level": 0,
    "indent_with_tabs": false,
    "preserve_newlines": true,
    "max_preserve_newlines": 2,
    "jslint_happy": false,
    "space_after_anon_function": false,
    "brace_style": "collapse-preserve-inline",
    "keep_array_indentation": false,
    "keep_function_indentation": false,
    "space_before_conditional": true,
    "break_chained_methods": false,
    "eval_code": false,
    "unescape_strings": false,
    "wrap_line_length": 0,
    "wrap_attributes": "auto",
    "wrap_attributes_indent_size": 4,
    "end_with_newline": true,
    "operator_position": "preserve-newline"
}

@olsonpm
Copy link
Contributor Author

olsonpm commented Mar 21, 2016

Hey thanks much - I'll take a look at this tonight when I'm back from work.

@olsonpm
Copy link
Contributor Author

olsonpm commented Mar 21, 2016

@nerdpad - 1.6.2 doesn't have this functionality. Please let me know what led you to believe this was implemented so we can update misleading documentation.

1.6.2 was committed jan 31, this patch was merged on feb 3. Not sure this patch will go in 1.6 as it does modify existing behavior - albeit that behavior is more a side effect than purposeful.

@nerdpad
Copy link

nerdpad commented Mar 21, 2016

@olsonpm I saw the commit was merged, so I assumed it was available in the latest version. What I didn't check was the release date of 1.6.2. Sorry about that.

Do we know when this patch will get in the main release?

@olsonpm
Copy link
Contributor Author

olsonpm commented Mar 21, 2016

Not a problem - I only asked in case we did anything wrong.

and @bitwiseman will be able to answer that question.

@bitwiseman
Copy link
Member

Doh, yeah, it went in just after 1.6.2. I need to get 1.6.3 published. 😄

@nerdpad
Copy link

nerdpad commented May 16, 2016

@bitwiseman Please publish 1.6.3. :)

@bitwiseman
Copy link
Member

Thanks for the reminder. 😄

@bitwiseman
Copy link
Member

Done! Enjoy!

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