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

Format on type fixes #1798

Merged
merged 104 commits into from
May 31, 2018
Merged
Show file tree
Hide file tree
Changes from 103 commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
a764bc9
Undo changes
Feb 13, 2018
9d1b2cc
Test fixes
Feb 13, 2018
a91291a
Increase timeout
Mar 2, 2018
bf266af
Remove double event listening
Mar 7, 2018
7bc6bd6
Remove test
Mar 7, 2018
8ce8b48
Revert "Remove test"
Mar 7, 2018
e3a549e
Revert "Remove double event listening"
Mar 7, 2018
92e8c1e
#1096 The if statement is automatically formatted incorrectly
Mar 27, 2018
b540a1d
Merge fix
Mar 27, 2018
7b0573e
Add more tests
Mar 27, 2018
facb106
More tests
Mar 27, 2018
f113881
Typo
Mar 27, 2018
3e76718
Test
Mar 28, 2018
6e85dc6
Also better handle multiline arguments
Mar 28, 2018
99e037c
Add a couple missing periods
brettcannon Mar 28, 2018
3caeab7
Undo changes
Feb 13, 2018
eeb1f11
Test fixes
Feb 13, 2018
f5f78c7
Increase timeout
Mar 2, 2018
88744da
Remove double event listening
Mar 7, 2018
65dde44
Remove test
Mar 7, 2018
c513f71
Revert "Remove test"
Mar 7, 2018
ccb3886
Revert "Remove double event listening"
Mar 7, 2018
106f4db
Merge fix
Mar 27, 2018
9e5cb43
Merge branch 'master' of https://github.com/MikhailArkhipov/vscode-py…
Apr 5, 2018
e1da6a6
#1257 On type formatting errors for args and kwargs
Apr 5, 2018
e78f0fb
Handle f-strings
Apr 5, 2018
725cf71
Stop importing from test code
Apr 5, 2018
5cd6d45
#1308 Single line statements leading to an indentation on the next line
Apr 5, 2018
27613db
#726 editing python after inline if statement invalid indent
Apr 5, 2018
8061a20
Undo change
Apr 5, 2018
17dc292
Move constant
Apr 5, 2018
65964b9
Harden LS startup error checks
Apr 10, 2018
4bf5a4c
#1364 Intellisense doesn't work after specific const string
Apr 10, 2018
6f7212c
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Apr 12, 2018
ddbd295
Telemetry for the analysis enging
Apr 12, 2018
ffd1d3f
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Apr 12, 2018
d4afb6c
PR feedback
Apr 13, 2018
12186b8
Fix typo
Apr 16, 2018
ca90529
Test baseline update
Apr 16, 2018
a7267b5
Jedi 0.12
Apr 16, 2018
cfee109
Priority to goto_defition
Apr 16, 2018
1285789
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Apr 17, 2018
d1ff1d9
News
Apr 17, 2018
1bd1651
Replace unzip
Apr 17, 2018
a69b6fd
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Apr 17, 2018
f916ace
Linux flavors + test
Apr 18, 2018
28ca25f
Grammar check
Apr 19, 2018
ad9a3c9
Grammar test
Apr 20, 2018
ff8dd35
Test baselines
Apr 20, 2018
26726f8
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Apr 20, 2018
d7806ca
Add news
Apr 20, 2018
0b3f316
Pin dependency
brettcannon Apr 23, 2018
28a8950
Merge branch 'grammar' of https://github.com/MikhailArkhipov/vscode-p…
Apr 23, 2018
1804617
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Apr 23, 2018
f000e5d
Specify markdown as preferable format
Apr 26, 2018
a06fd79
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Apr 26, 2018
ef7c5c7
Improve function argument detection
Apr 26, 2018
f4e88c0
Specify markdown
Apr 27, 2018
d420c34
Merge branch 'master' of https://github.com/Microsoft/vscode-python
May 3, 2018
b819d57
Merge branch 'master' into analysis
May 3, 2018
abff213
Pythia setting
May 3, 2018
d140b3a
Baseline updates
May 3, 2018
4b394d9
Baseline update
May 3, 2018
a397b11
Improve startup
May 4, 2018
e54eaf8
Handle missing interpreter better
May 4, 2018
3b8ddd5
Handle interpreter change
May 4, 2018
9a4500d
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
May 4, 2018
41f9624
Delete old file
May 4, 2018
3627b85
Fix LS startup time reporting
May 4, 2018
486d11d
Remove Async suffix from IFileSystem
May 8, 2018
cf5cf9c
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
May 8, 2018
4913e28
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
May 18, 2018
84214e1
Remove Pythia
May 17, 2018
9c1adb1
Remove pre-packaged MSIL
May 18, 2018
5a6e546
Exe name on Unix
May 18, 2018
1f2ae09
Plain linux
May 18, 2018
f972614
Fix casing
May 19, 2018
e0021a9
Merge branch 'analysis' of https://github.com/MikhailArkhipov/vscode-…
May 19, 2018
d2721cd
Fix message
May 19, 2018
b8bc0a2
Merge branch 'analysis' of https://github.com/MikhailArkhipov/vscode-…
May 19, 2018
56d34f7
Update PTVS engine activation steps
May 21, 2018
9aab160
Merge branch 'analysis' of https://github.com/MikhailArkhipov/vscode-…
May 21, 2018
981290f
Type formatter eats space in from .
May 22, 2018
d279e96
fIX CASING
May 22, 2018
6b466a9
Remove flag
May 23, 2018
d8c6193
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
May 23, 2018
2904f3b
Don't wait for LS
May 24, 2018
c7d34c9
Small test fixes
May 28, 2018
17775bd
Update hover baselines
May 28, 2018
2edeb3c
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
May 29, 2018
2fd5387
Rename the engine
May 29, 2018
a06b993
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
May 29, 2018
0190078
Formatting 1
May 29, 2018
5b93e34
Add support for 'rf' strings
May 30, 2018
781e6b1
Add two spaces before comment per PEP
May 30, 2018
e795309
Fix @ operator spacing
May 30, 2018
d300d0c
Handle module and unary ops
May 30, 2018
dd09087
Type hints
May 30, 2018
46b6dfd
Fix typo
May 30, 2018
cf264b8
Trailing comma
May 30, 2018
3e341e9
Require space after if
May 30, 2018
e5a588e
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
May 30, 2018
06e7140
Update list of keywords
May 30, 2018
c597d82
PR feedback
May 31, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 63 additions & 19 deletions src/client/formatters/lineFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ import { TextRangeCollection } from '../language/textRangeCollection';
import { Tokenizer } from '../language/tokenizer';
import { ITextRangeCollection, IToken, TokenType } from '../language/types';

const keywordsWithSpaceBeforeBrace = [
'and', 'as', 'assert',
Copy link

@DonJayamanne DonJayamanne May 30, 2018

Choose a reason for hiding this comment

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

Please add await as well (#1786 (comment)).

Copy link
Member

Choose a reason for hiding this comment

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

A complete list of keywords can be found using https://docs.python.org/3/library/keyword.html#keyword.kwlist

Copy link
Author

Choose a reason for hiding this comment

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

This is list of those that may have ( after them. Does not have to be complete.

'del',
'except', 'elif',
'for', 'from',
'global',
'if', 'import', 'in', 'is',
'nonlocal', 'not',
'or',
'raise', 'return',
'while', 'with',
'yield'
];

export class LineFormatter {
private builder = new TextBuilder();
private tokens: ITextRangeCollection<IToken> = new TextRangeCollection<IToken>([]);
Expand Down Expand Up @@ -59,7 +73,7 @@ export class LineFormatter {
}
const id = this.text.substring(t.start, t.end);
this.builder.append(id);
if (this.keywordWithSpaceAfter(id) && next && this.isOpenBraceType(next.type)) {
if (this.isKeywordWithSpaceBeforeBrace(id) && next && this.isOpenBraceType(next.type)) {
// for x in ()
this.builder.softAppendSpace();
}
Expand All @@ -75,9 +89,9 @@ export class LineFormatter {
break;

case TokenType.Comment:
// Add space before in-line comment.
// Add 2 spaces before in-line comment per PEP guidelines.
if (prev) {
this.builder.softAppendSpace();
this.builder.softAppendSpace(2);
}
this.builder.append(this.text.substring(t.start, t.end));
break;
Expand All @@ -98,28 +112,35 @@ export class LineFormatter {
private handleOperator(index: number): void {
const t = this.tokens.getItemAt(index);
const prev = index > 0 ? this.tokens.getItemAt(index - 1) : undefined;
const opCode = this.text.charCodeAt(t.start);
const next = index < this.tokens.count - 1 ? this.tokens.getItemAt(index + 1) : undefined;

if (t.length === 1) {
const opCode = this.text.charCodeAt(t.start);
switch (opCode) {
case Char.Equal:
if (this.handleEqual(t, index)) {
return;
}
break;
this.handleEqual(t, index);
return;
case Char.Period:
if (prev && this.isKeyword(prev, 'from')) {
this.builder.softAppendSpace();
}
this.builder.append(this.text[t.start]);
this.builder.append('.');
if (next && this.isKeyword(next, 'import')) {
this.builder.softAppendSpace();
}
return;
case Char.At:
if (prev) {
// Binary case
this.builder.softAppendSpace();
this.builder.append('@');
this.builder.softAppendSpace();
} else {
this.builder.append('@');
}
return;
case Char.ExclamationMark:
this.builder.append(this.text[t.start]);
this.builder.append('!');
return;
case Char.Asterisk:
if (prev && this.isKeyword(prev, 'lambda')) {
Expand Down Expand Up @@ -153,19 +174,34 @@ export class LineFormatter {

this.builder.softAppendSpace();
this.builder.append(this.text.substring(t.start, t.end));

// Check unary case
if (prev && prev.type === TokenType.Operator) {
if (opCode === Char.Hyphen || opCode === Char.Plus || opCode === Char.Tilde) {
return;
}
}
this.builder.softAppendSpace();
}

private handleEqual(t: IToken, index: number): boolean {
private handleEqual(t: IToken, index: number): void {
if (this.isMultipleStatements(index) && !this.braceCounter.isOpened(TokenType.OpenBrace)) {
return false; // x = 1; x, y = y, x
// x = 1; x, y = y, x
this.builder.softAppendSpace();
this.builder.append('=');
this.builder.softAppendSpace();
return;
}

// Check if this is = in function arguments. If so, do not add spaces around it.
if (this.isEqualsInsideArguments(index)) {
this.builder.append('=');
return true;
return;
}
return false;

this.builder.softAppendSpace();
this.builder.append('=');
this.builder.softAppendSpace();
}

private handleOther(t: IToken, index: number): void {
Expand All @@ -188,6 +224,12 @@ export class LineFormatter {
return;
}

if (t.type === TokenType.Number && prev && prev.type === TokenType.Operator && prev.length === 1 && this.text.charCodeAt(prev.start) === Char.Tilde) {
// Special case for ~ before numbers
this.builder.append(this.text.substring(t.start, t.end));
return;
}

if (t.type === TokenType.Unknown) {
this.handleUnknown(t);
} else {
Expand Down Expand Up @@ -224,6 +266,10 @@ export class LineFormatter {
return false;
}

if (index > 1 && this.tokens.getItemAt(index - 2).type === TokenType.Colon) {
return false; // Type hint should have spaces around like foo(x: int = 1) per PEP 8
}

const first = this.tokens.getItemAt(0);
if (first.type === TokenType.Comma) {
return true; // Line starts with commma
Expand Down Expand Up @@ -278,11 +324,9 @@ export class LineFormatter {
}
return false;
}
private keywordWithSpaceAfter(s: string): boolean {
return s === 'in' || s === 'return' || s === 'and' ||
s === 'or' || s === 'not' || s === 'from' ||
s === 'import' || s === 'except' || s === 'for' ||
s === 'as' || s === 'is';

private isKeywordWithSpaceBeforeBrace(s: string): boolean {
return keywordsWithSpaceBeforeBrace.indexOf(s) >= 0;
}
private isKeyword(t: IToken, keyword: string): boolean {
return t.type === TokenType.Identifier && t.length === keyword.length && this.text.substr(t.start, t.length) === keyword;
Expand Down
10 changes: 8 additions & 2 deletions src/client/language/textBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ export class TextBuilder {
return this.segments.join('');
}

public softAppendSpace(): void {
if (!this.isLastWhiteSpace() && this.segments.length > 0) {
public softAppendSpace(count: number = 1): void {
if (this.segments.length === 0) {
return;
}
if (this.isLastWhiteSpace()) {
count = count - 1;
}
for (let i = 0; i < count; i += 1) {
this.segments.push(' ');
}
}
Expand Down
43 changes: 31 additions & 12 deletions src/client/language/tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ export class Tokenizer implements ITokenizer {
case Char.Caret:
case Char.Equal:
case Char.ExclamationMark:
case Char.Percent:
case Char.Tilde:
length = nextChar === Char.Equal ? 2 : 1;
break;

Expand Down Expand Up @@ -350,23 +352,40 @@ export class Tokenizer implements ITokenizer {
this.tokens.push(new Token(TokenType.Comment, start, this.cs.position - start));
}

// tslint:disable-next-line:cyclomatic-complexity
private getStringPrefixLength(): number {
if (this.cs.currentChar === Char.f && (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote)) {
return 1; // f-string
if (this.cs.currentChar === Char.SingleQuote || this.cs.currentChar === Char.DoubleQuote) {
return 0; // Simple string, no prefix
}
if (this.cs.currentChar === Char.b || this.cs.currentChar === Char.B || this.cs.currentChar === Char.u || this.cs.currentChar === Char.U) {
if (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote) {
// b-string or u-string
return 1;

if (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote) {
switch (this.cs.currentChar) {
case Char.f:
case Char.F:
case Char.r:
case Char.R:
case Char.b:
case Char.B:
case Char.u:
case Char.U:
return 1; // single-char prefix like u"" or r""
default:
break;
}
if (this.cs.nextChar === Char.r || this.cs.nextChar === Char.R) {
// b-string or u-string with 'r' suffix
if (this.cs.lookAhead(2) === Char.SingleQuote || this.cs.lookAhead(2) === Char.DoubleQuote) {
return 2;
}
}

if (this.cs.lookAhead(2) === Char.SingleQuote || this.cs.lookAhead(2) === Char.DoubleQuote) {
const prefix = this.cs.getText().substr(this.cs.position, 2).toLowerCase();
switch (prefix) {
case 'rf':
case 'ur':
case 'br':
return 2;
default:
break;
}
}
return this.cs.currentChar === Char.SingleQuote || this.cs.currentChar === Char.DoubleQuote ? 0 : -1;
return -1;
}

private getQuoteType(): QuoteType {
Expand Down
34 changes: 33 additions & 1 deletion src/test/format/extension.lineFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ suite('Formatting - line formatter', () => {
testFormatLine('[ 1 :[2: (x,),y]]{1}', '[1:[2:(x,), y]]{1}');
});
test('Trailing comment', () => {
testFormatLine('x=1 # comment', 'x = 1 # comment');
testFormatLine('x=1 # comment', 'x = 1 # comment');
});
test('Single comment', () => {
testFormatLine('# comment', '# comment');
Expand Down Expand Up @@ -87,9 +87,15 @@ suite('Formatting - line formatter', () => {
});
test('Brace after keyword', () => {
testFormatLine('for x in(1,2,3)', 'for x in (1, 2, 3)');
testFormatLine('assert(1,2,3)', 'assert (1, 2, 3)');
testFormatLine('if (True|False)and(False/True)not (! x )', 'if (True | False) and (False / True) not (!x)');
testFormatLine('while (True|False)', 'while (True | False)');
testFormatLine('yield(a%b)', 'yield (a % b)');
});
test('Dot operator', () => {
testFormatLine('x.y', 'x.y');
testFormatLine('5 .y', '5.y');
//testFormatLine('- 135 .bit_length()', '-135.bit_length()');

Choose a reason for hiding this comment

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

Any particular reason why this test is commented out?

Copy link
Author

Choose a reason for hiding this comment

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

I am yet to get to #1784, quite an edge case. The - part is covered by another test.

});
test('Unknown tokens no space', () => {
testFormatLine('abc\\n\\', 'abc\\n\\');
Expand Down Expand Up @@ -121,6 +127,32 @@ suite('Formatting - line formatter', () => {
test('from..x import', () => {
testFormatLine('from..x import', 'from ..x import');
});
test('Raw strings', () => {
testFormatLine('z=r""', 'z = r""');
testFormatLine('z=rf""', 'z = rf""');
testFormatLine('z=R""', 'z = R""');
testFormatLine('z=RF""', 'z = RF""');
});
test('Binary @', () => {
testFormatLine('a@ b', 'a @ b');
});
test('Unary operators', () => {
testFormatLine('x= - y', 'x = -y');
testFormatLine('x= + y', 'x = +y');
testFormatLine('x= ~ y', 'x = ~y');
testFormatLine('x=-1', 'x = -1');
testFormatLine('x= +1', 'x = +1');
testFormatLine('x= ~1 ', 'x = ~1');
});
test('Equals with type hints', () => {
testFormatLine('def foo(x:int=3,x=100.)', 'def foo(x: int = 3, x=100.)');
});
test('Trailing comma', () => {
testFormatLine('a, =[1]', 'a, = [1]');
});
test('if()', () => {
testFormatLine('if(True) :', 'if (True):');
});
test('Grammar file', () => {
const content = fs.readFileSync(grammarFile).toString('utf8');
const lines = content.splitLines({ trim: false, removeEmptyEntries: false });
Expand Down
27 changes: 22 additions & 5 deletions src/test/language/tokenizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ suite('Language.Tokenizer', () => {
});
test('Unknown token', () => {
const t = new Tokenizer();
const tokens = t.tokenize('~$');
const tokens = t.tokenize('`$');
assert.equal(tokens.count, 1);

assert.equal(tokens.getItemAt(0).type, TokenType.Unknown);
Expand Down Expand Up @@ -301,20 +301,37 @@ suite('Language.Tokenizer', () => {
assert.equal(tokens.getItemAt(5).type, TokenType.Number);
assert.equal(tokens.getItemAt(5).length, 5);
});
test('Simple expression, leading minus', () => {
const t = new Tokenizer();
const tokens = t.tokenize('x == -y');
assert.equal(tokens.count, 4);

assert.equal(tokens.getItemAt(0).type, TokenType.Identifier);
assert.equal(tokens.getItemAt(0).length, 1);

assert.equal(tokens.getItemAt(1).type, TokenType.Operator);
assert.equal(tokens.getItemAt(1).length, 2);

assert.equal(tokens.getItemAt(2).type, TokenType.Operator);
assert.equal(tokens.getItemAt(2).length, 1);

assert.equal(tokens.getItemAt(3).type, TokenType.Identifier);
assert.equal(tokens.getItemAt(3).length, 1);
});
test('Operators', () => {
const text = '< <> << <<= ' +
'== != > >> >>= >= <=' +
'+ -' +
'+ - ~ %' +
'* ** / /= //=' +
'*= += -= **= ' +
'*= += -= ~= %= **= ' +
'& &= | |= ^ ^= ->';
const tokens = new Tokenizer().tokenize(text);
const lengths = [
1, 2, 2, 3,
2, 2, 1, 2, 3, 2, 2,
1, 1,
1, 1, 1, 1,
1, 2, 1, 2, 3,
2, 2, 2, 3,
2, 2, 2, 2, 2, 3,
1, 2, 1, 2, 1, 2, 2];
assert.equal(tokens.count, lengths.length);
for (let i = 0; i < tokens.count; i += 1) {
Expand Down
6 changes: 3 additions & 3 deletions src/test/pythonFiles/formatting/pythonGrammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def test_eof_error(self):
compile(s, "<test>", "exec")
self.assertIn("unexpected EOF", str(cm.exception))

var_annot_global: int # a global annotated is necessary for test_var_annot
var_annot_global: int # a global annotated is necessary for test_var_annot

# custom namespace for testing __annotations__

Expand Down Expand Up @@ -643,7 +643,7 @@ def test_lambdef(self):
### lambdef: 'lambda' [varargslist] ':' test
l1 = lambda: 0
self.assertEqual(l1(), 0)
l2 = lambda: a[d] # XXX just testing the expression
l2 = lambda: a[d] # XXX just testing the expression
l3 = lambda: [2 < x for x in [-1, 3, 0]]
self.assertEqual(l3(), [0, 1, 0])
l4 = lambda x=lambda y=lambda z=1: z: y(): x()
Expand Down Expand Up @@ -1492,7 +1492,7 @@ def __imatmul__(self, o):
self.other = o
return self
m = M()
self.assertEqual(m@m, 4)
self.assertEqual(m @ m, 4)
m @= 42
self.assertEqual(m.other, 42)

Expand Down