Skip to content

Commit

Permalink
Make maximum parse length configurable (mark-wiemer-org#337)
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-wiemer committed Jan 31, 2023
1 parent dd1c853 commit 3e890a8
Show file tree
Hide file tree
Showing 10 changed files with 10,116 additions and 15 deletions.
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@
"default": "C:/Program Files/AutoHotkey/AutoHotkey.chm",
"description": "Path to the AHK Help document."
},
"ahk++.file.maximumParseLength": {
"type": "number",
"default": 10000,
"description": "Number of lines to parse for each AHK file. Larger numbers may impact performance. Parsing is used by IntelliSense only, not AHK itself. Changing this value will not affect the behavior of AHK scripts.\n-1: Unlimited parsing.\n0: No parsing."
},
"ahk++.file.templateSnippetName": {
"type": "string",
"default": "AhkTemplate",
Expand All @@ -135,7 +140,7 @@
"type": "integer",
"default": 1,
"minimum": -1,
"description": "Allowed number of empty lines.\n-1: ignore empty lines.\n0: no empty lines."
"description": "Allowed number of empty lines.\n-1: Ignore empty lines.\n0: No empty lines."
},
"ahk++.formatter.indentCodeAfterLabel": {
"type": "boolean",
Expand Down
1 change: 1 addition & 0 deletions src/common/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export enum ConfigKey {
helpPath = 'file.helpPath',
indentCodeAfterLabel = 'formatter.indentCodeAfterLabel',
indentCodeAfterSharpDirective = 'formatter.indentCodeAfterSharpDirective',
maximumParseLength = 'file.maximumParseLength',
preserveIndent = 'formatter.preserveIndent',
templateSnippetName = 'file.templateSnippetName',
trimExtraSpaces = 'formatter.trimExtraSpaces',
Expand Down
34 changes: 27 additions & 7 deletions src/parser/parser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { ConfigKey, Global } from '../common/global';
import * as fs from 'fs';
import * as vscode from 'vscode';
import { CodeUtil } from '../common/codeUtil';
import { Out } from '../common/out';
import { Script, Method, Ref, Label, Block, Variable } from './model';

export interface BuildScriptOptions {
/** Defaults to false. If true, short-circuits when document is in cache. */
usingCache?: boolean;
/** Lines to parse. Defaults to extension setting. -1 for unlimited parsing. 0 for no parsing. */
maximumParseLength?: number;
}

export class Parser {
private static documentCache = new Map<string, Script>();

Expand Down Expand Up @@ -42,27 +50,36 @@ export class Parser {
*/
public static async buildScript(
document: vscode.TextDocument,
usingCache = false,
options: BuildScriptOptions = {},
): Promise<Script> {
if (usingCache && this.documentCache.get(document.uri.path)) {
if (options.usingCache && this.documentCache.get(document.uri.path)) {
return this.documentCache.get(document.uri.path);
}

const maxParseLength =
options.maximumParseLength ??
Global.getConfig<number>(ConfigKey.maximumParseLength);
// limit parse length for performance
/** Count of lines to parse */
const linesToParse =
maxParseLength >= 0
? Math.min(document.lineCount, maxParseLength)
: document.lineCount;

const methods: Method[] = [];
const refs: Ref[] = [];
const labels: Label[] = [];
const variables: Variable[] = [];
const blocks: Block[] = [];
let currentMethod: Method;
let deep = 0;
const lineCount = Math.min(document.lineCount, 10000);
let blockComment = false;
for (let line = 0; line < lineCount; line++) {
for (let line = 0; line < linesToParse; line++) {
const lineText = document.lineAt(line).text;
if (lineText.match(/ *\/\*/)) {
if (lineText.match(startBlockComment)) {
blockComment = true;
}
if (lineText.match(/ *\*\//)) {
if (lineText.match(endBlockComment)) {
blockComment = false;
}
if (blockComment) {
Expand Down Expand Up @@ -277,7 +294,7 @@ export class Parser {
document: vscode.TextDocument,
line: number,
origin?: string,
) {
): Method | Ref | Ref[] {
origin ??= document.lineAt(line).text;
const text = CodeUtil.purify(origin);
// [\u4e00-\u9fa5] Chinese unicode characters
Expand Down Expand Up @@ -374,3 +391,6 @@ export class Parser {
}
}
}

const startBlockComment = / *\/\*/;
const endBlockComment = / *\*\//;
2 changes: 1 addition & 1 deletion src/providers/completionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class CompletionProvider implements vscode.CompletionItemProvider {
}
});

const script = await Parser.buildScript(document, true);
const script = await Parser.buildScript(document, { usingCache: true });
script.variables.forEach((variable) => {
const completionItem = new vscode.CompletionItem(
variable.name,
Expand Down
2 changes: 1 addition & 1 deletion src/providers/defProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class DefProvider implements vscode.DefinitionProvider {
);
}

const script = await Parser.buildScript(document, true);
const script = await Parser.buildScript(document, { usingCache: true });

for (const method of script.methods) {
if (
Expand Down
2 changes: 1 addition & 1 deletion src/providers/symbolProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class SymbolProvider implements vscode.DocumentSymbolProvider {
): Promise<vscode.DocumentSymbol[]> {
const result = [];

const script = await Parser.buildScript(document, false);
const script = await Parser.buildScript(document);

for (const method of script.methods) {
result.push(
Expand Down
9 changes: 5 additions & 4 deletions src/test/suite/format/format.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getDocument } from '../../utils';
import * as assert from 'assert';
import * as fs from 'fs-extra';
import * as path from 'path';
Expand Down Expand Up @@ -148,6 +149,7 @@ suite('Internal formatter', () => {
suite('External formatter', () => {
// test external formatter a few times to make sure the connection is working
// advanced tests are for internal formatter only
// TODO note these tests only support editor settings, not extension settings
const externalFormatTests: FormatTest[] = [
{ filenameRoot: '25-multiline-string' },
{
Expand All @@ -168,10 +170,9 @@ suite('External formatter', () => {
const outFileString = fs
.readFileSync(path.join(filesParentPath, outFilename))
.toString();
const unformattedSampleFile =
await vscode.workspace.openTextDocument(
path.join(filesParentPath, inFilename),
);
const unformattedSampleFile = await getDocument(
path.join(filesParentPath, inFilename),
);
const originalText = unformattedSampleFile.getText();
const textEditor = await vscode.window.showTextDocument(
unformattedSampleFile,
Expand Down
61 changes: 61 additions & 0 deletions src/test/suite/parser/parser.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { getDocument } from '../../utils';
import * as assert from 'assert';
import * as path from 'path';
import * as vscode from 'vscode';
import { Parser } from '../../../parser/Parser';

Expand Down Expand Up @@ -72,4 +74,63 @@ suite('Parser', () => {
});
});
});

suite('buildScript', () => {
// Currently in `out` folder, need to get back to main `src` folder
const filesParentPath = path.join(
__dirname,
'..',
'..',
'..',
'..',
'src',
'test',
'suite',
'parser',
'samples',
);

const myTests: {
name: string;
maximumParseLength: number;
expectedMethodCount: number;
}[] = [
{
name: 'stops at provided max parse length',
maximumParseLength: 10_000,
expectedMethodCount: 1,
},
{
name: 'respects parse lengths higher than ten thousand',
maximumParseLength: 11_000,
expectedMethodCount: 2,
},
{
name: '-1 means unlimited parsing',
maximumParseLength: -1,
expectedMethodCount: 2,
},
{
name: '0 means no parsing',
maximumParseLength: 0,
expectedMethodCount: 0,
},
];

myTests.forEach((myTest) =>
test(myTest.name, async () => {
const filename = '117-ten-thousand-lines.ahk';
const document = await getDocument(
path.join(filesParentPath, filename),
);
const result = await Parser.buildScript(document, {
maximumParseLength: myTest.maximumParseLength,
});
assert.strictEqual(
result.methods.length,
myTest.expectedMethodCount,
);
}),
);
});
});
Loading

0 comments on commit 3e890a8

Please sign in to comment.