Skip to content

Commit db6030d

Browse files
author
Mikhail Arkhipov
authored
Fix indentation on single-liners and f-strings in type formatting (#1312)
* Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3f. * Revert "Remove double event listening" This reverts commit af573be. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3f. * Revert "Remove double event listening" This reverts commit af573be. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant
1 parent 72ef2ab commit db6030d

File tree

6 files changed

+71
-11
lines changed

6 files changed

+71
-11
lines changed

src/client/common/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,8 @@ export function isTestExecution(): boolean {
6969
// tslint:disable-next-line:interface-name no-string-literal
7070
return process.env['VSC_PYTHON_CI_TEST'] === '1';
7171
}
72+
export function isPythonAnalysisEngineTest(): boolean {
73+
return process.env.VSC_PYTHON_ANALYSIS === '1';
74+
}
7275

7376
export const EXTENSION_ROOT_DIR = path.join(__dirname, '..', '..', '..');

src/client/extension.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ import {
1111
extensions, IndentAction, languages, Memento,
1212
OutputChannel, window
1313
} from 'vscode';
14-
import { IS_ANALYSIS_ENGINE_TEST } from '../test/constants';
1514
import { AnalysisExtensionActivator } from './activation/analysis';
1615
import { ClassicExtensionActivator } from './activation/classic';
1716
import { IExtensionActivator } from './activation/types';
1817
import { PythonSettings } from './common/configSettings';
19-
import { STANDARD_OUTPUT_CHANNEL } from './common/constants';
18+
import { isPythonAnalysisEngineTest, STANDARD_OUTPUT_CHANNEL } from './common/constants';
2019
import { FeatureDeprecationManager } from './common/featureDeprecationManager';
2120
import { createDeferred } from './common/helpers';
2221
import { PythonInstaller } from './common/installer/pythonInstallation';
@@ -75,7 +74,7 @@ export async function activate(context: ExtensionContext) {
7574
const configuration = serviceManager.get<IConfigurationService>(IConfigurationService);
7675
const pythonSettings = configuration.getSettings();
7776

78-
const activator: IExtensionActivator = IS_ANALYSIS_ENGINE_TEST || !pythonSettings.jediEnabled
77+
const activator: IExtensionActivator = isPythonAnalysisEngineTest() || !pythonSettings.jediEnabled
7978
? new AnalysisExtensionActivator(serviceManager, pythonSettings)
8079
: new ClassicExtensionActivator(serviceManager, pythonSettings);
8180

@@ -108,7 +107,11 @@ export async function activate(context: ExtensionContext) {
108107
languages.setLanguageConfiguration(PYTHON.language!, {
109108
onEnterRules: [
110109
{
111-
beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*/,
110+
beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except)\b.*:\s*\S+/,
111+
action: { indentAction: IndentAction.None }
112+
},
113+
{
114+
beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*:\s*/,
112115
action: { indentAction: IndentAction.Indent }
113116
},
114117
{

src/client/formatters/lineFormatter.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,22 @@ export class LineFormatter {
9595
}
9696
break;
9797
case Char.Period:
98-
this.builder.append('.');
99-
return;
10098
case Char.At:
101-
this.builder.append('@');
99+
case Char.ExclamationMark:
100+
this.builder.append(this.text[t.start]);
102101
return;
103102
default:
104103
break;
105104
}
106105
}
106+
// Do not append space if operator is preceded by '(' or ',' as in foo(**kwarg)
107+
if (index > 0) {
108+
const prev = this.tokens.getItemAt(index - 1);
109+
if (this.isOpenBraceType(prev.type) || prev.type === TokenType.Comma) {
110+
this.builder.append(this.text.substring(t.start, t.end));
111+
return;
112+
}
113+
}
107114
this.builder.softAppendSpace();
108115
this.builder.append(this.text.substring(t.start, t.end));
109116
this.builder.softAppendSpace();

src/client/language/tokenizer.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,16 @@ export class Tokenizer implements ITokenizer {
8585
}
8686
}
8787

88+
// tslint:disable-next-line:cyclomatic-complexity
8889
private handleCharacter(): boolean {
90+
// f-strings
91+
const fString = this.cs.currentChar === Char.f && (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote);
92+
if (fString) {
93+
this.cs.moveNext();
94+
}
8995
const quoteType = this.getQuoteType();
9096
if (quoteType !== QuoteType.None) {
91-
this.handleString(quoteType);
97+
this.handleString(quoteType, fString);
9298
return true;
9399
}
94100
if (this.cs.currentChar === Char.Hash) {
@@ -342,8 +348,8 @@ export class Tokenizer implements ITokenizer {
342348
return QuoteType.None;
343349
}
344350

345-
private handleString(quoteType: QuoteType): void {
346-
const start = this.cs.position;
351+
private handleString(quoteType: QuoteType, fString: boolean): void {
352+
const start = fString ? this.cs.position - 1 : this.cs.position;
347353
if (quoteType === QuoteType.Single || quoteType === QuoteType.Double) {
348354
this.cs.moveNext();
349355
this.skipToSingleEndQuote(quoteType === QuoteType.Single

src/test/format/extension.lineFormatter.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,16 @@ suite('Formatting - line formatter', () => {
7373
const actual = formatter.formatLine('foo(x,y= \"a\",');
7474
assert.equal(actual, 'foo(x, y=\"a\",');
7575
});
76-
test('Equals in multiline arguments', () => {
76+
test('Equals in multiline arguments', () => {
7777
const actual = formatter.formatLine('x = 1,y =-2)');
7878
assert.equal(actual, 'x=1, y=-2)');
7979
});
8080
test('Equals in multiline arguments starting comma', () => {
8181
const actual = formatter.formatLine(',x = 1,y =m)');
8282
assert.equal(actual, ', x=1, y=m)');
8383
});
84+
test('Operators without following space', () => {
85+
const actual = formatter.formatLine('foo( *a, ** b, ! c)');
86+
assert.equal(actual, 'foo(*a, **b, !c)');
87+
});
8488
});

src/test/language/tokenizer.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,43 @@ suite('Language.Tokenizer', () => {
7979
assert.equal(tokens.getItemAt(0).type, TokenType.String);
8080
assert.equal(tokens.getItemAt(0).length, 12);
8181
});
82+
test('Strings: single quoted f-string ', async () => {
83+
const t = new Tokenizer();
84+
// tslint:disable-next-line:quotemark
85+
const tokens = t.tokenize("a+f'quoted'");
86+
assert.equal(tokens.count, 3);
87+
assert.equal(tokens.getItemAt(0).type, TokenType.Identifier);
88+
assert.equal(tokens.getItemAt(1).type, TokenType.Operator);
89+
assert.equal(tokens.getItemAt(2).type, TokenType.String);
90+
assert.equal(tokens.getItemAt(2).length, 9);
91+
});
92+
test('Strings: double quoted f-string ', async () => {
93+
const t = new Tokenizer();
94+
const tokens = t.tokenize('x(1,f"quoted")');
95+
assert.equal(tokens.count, 6);
96+
assert.equal(tokens.getItemAt(0).type, TokenType.Identifier);
97+
assert.equal(tokens.getItemAt(1).type, TokenType.OpenBrace);
98+
assert.equal(tokens.getItemAt(2).type, TokenType.Number);
99+
assert.equal(tokens.getItemAt(3).type, TokenType.Comma);
100+
assert.equal(tokens.getItemAt(4).type, TokenType.String);
101+
assert.equal(tokens.getItemAt(4).length, 9);
102+
assert.equal(tokens.getItemAt(5).type, TokenType.CloseBrace);
103+
});
104+
test('Strings: single quoted multiline f-string ', async () => {
105+
const t = new Tokenizer();
106+
// tslint:disable-next-line:quotemark
107+
const tokens = t.tokenize("f'''quoted'''");
108+
assert.equal(tokens.count, 1);
109+
assert.equal(tokens.getItemAt(0).type, TokenType.String);
110+
assert.equal(tokens.getItemAt(0).length, 13);
111+
});
112+
test('Strings: double quoted multiline f-string ', async () => {
113+
const t = new Tokenizer();
114+
const tokens = t.tokenize('f"""quoted """');
115+
assert.equal(tokens.count, 1);
116+
assert.equal(tokens.getItemAt(0).type, TokenType.String);
117+
assert.equal(tokens.getItemAt(0).length, 14);
118+
});
82119
test('Comments', async () => {
83120
const t = new Tokenizer();
84121
const tokens = t.tokenize(' #co"""mment1\n\t\n#comm\'ent2 ');

0 commit comments

Comments
 (0)