Skip to content

Commit 72c71ea

Browse files
committed
RulesProvider performance improvements
1 parent 9796557 commit 72c71ea

File tree

5 files changed

+108
-42
lines changed

5 files changed

+108
-42
lines changed

src/server/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ namespace ts.server {
205205
this.lsHost = new LSHost(this.projectService.host, this, this.projectService.cancellationToken);
206206
this.lsHost.setCompilationSettings(this.compilerOptions);
207207

208-
this.languageService = ts.createLanguageService(this.lsHost, this.documentRegistry);
208+
this.languageService = ts.createLanguageService(this.lsHost, this.documentRegistry, this.projectService.getFormatCodeOptions());
209209

210210
if (!languageServiceEnabled) {
211211
this.disableLanguageService();

src/services/formatting/rulesMap.ts

Lines changed: 84 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,55 @@
33
/* @internal */
44
namespace ts.formatting {
55
export class RulesMap {
6+
// This array is used during construction & updating of the rules buckets in the map
7+
private rulesBucketConstructionStateList: RulesBucketConstructionState[];
8+
private readonly lowPriorityCommonRules: Rule[];
9+
610
public map: RulesBucket[];
711
public mapRowLength: number;
812

9-
constructor() {
13+
constructor(lowPriorityCommonRules: Rule[]) {
1014
this.map = [];
1115
this.mapRowLength = 0;
16+
this.lowPriorityCommonRules = lowPriorityCommonRules;
1217
}
1318

14-
static create(rules: Rule[]): RulesMap {
15-
const result = new RulesMap();
16-
result.Initialize(rules);
19+
static create(highPriorityCommonRules: Rule[], lowPriorityCommonRules: Rule[]): RulesMap {
20+
const result = new RulesMap(lowPriorityCommonRules);
21+
result.Initialize(highPriorityCommonRules);
1722
return result;
1823
}
1924

25+
public Update(oldRules: Rule[], newRules: Rule[]): void {
26+
const addRules = filter(newRules, r => oldRules.indexOf(r) < 0);
27+
const deleteRules = filter(oldRules, r => newRules.indexOf(r) < 0);
28+
29+
if (addRules.length === 0 && deleteRules.length === 0) {
30+
return;
31+
}
32+
33+
this.RemoveRules(deleteRules.concat(this.lowPriorityCommonRules));
34+
this.FillRules(addRules.concat(this.lowPriorityCommonRules));
35+
}
36+
2037
public Initialize(rules: Rule[]) {
2138
this.mapRowLength = SyntaxKind.LastToken + 1;
2239
this.map = <any>new Array(this.mapRowLength * this.mapRowLength); // new Array<RulesBucket>(this.mapRowLength * this.mapRowLength);
40+
this.rulesBucketConstructionStateList = new Array(this.map.length); // new Array<RulesBucketConstructionState>(this.map.length);
2341

24-
// This array is used only during construction of the rulesbucket in the map
25-
const rulesBucketConstructionStateList: RulesBucketConstructionState[] = <any>new Array(this.map.length); // new Array<RulesBucketConstructionState>(this.map.length);
26-
27-
this.FillRules(rules, rulesBucketConstructionStateList);
42+
this.FillRules(rules);
2843
return this.map;
2944
}
3045

31-
public FillRules(rules: Rule[], rulesBucketConstructionStateList: RulesBucketConstructionState[]): void {
46+
public FillRules(rules: Rule[]): void {
47+
rules.forEach((rule) => {
48+
this.AddOrRemoveRule(rule, RulesAction.Add);
49+
});
50+
}
51+
52+
public RemoveRules(rules: Rule[]): void {
3253
rules.forEach((rule) => {
33-
this.FillRule(rule, rulesBucketConstructionStateList);
54+
this.AddOrRemoveRule(rule, RulesAction.Remove);
3455
});
3556
}
3657

@@ -40,7 +61,7 @@ namespace ts.formatting {
4061
return rulesBucketIndex;
4162
}
4263

43-
private FillRule(rule: Rule, rulesBucketConstructionStateList: RulesBucketConstructionState[]): void {
64+
private AddOrRemoveRule(rule: Rule, action: RulesAction): void {
4465
const specificRule = rule.Descriptor.LeftTokenRange !== Shared.TokenRange.Any &&
4566
rule.Descriptor.RightTokenRange !== Shared.TokenRange.Any;
4667

@@ -49,11 +70,19 @@ namespace ts.formatting {
4970
const rulesBucketIndex = this.GetRuleBucketIndex(left, right);
5071

5172
let rulesBucket = this.map[rulesBucketIndex];
52-
if (rulesBucket === undefined) {
53-
rulesBucket = this.map[rulesBucketIndex] = new RulesBucket();
73+
if (action === RulesAction.Add) {
74+
if (rulesBucket === undefined) {
75+
rulesBucket = this.map[rulesBucketIndex] = new RulesBucket();
76+
}
77+
rulesBucket.AddRule(rule, specificRule, this.rulesBucketConstructionStateList, rulesBucketIndex);
78+
}
79+
else {
80+
if (rulesBucket === undefined) {
81+
// The rules bucket does not exist for this rule
82+
return;
83+
}
84+
rulesBucket.RemoveRule(rule, specificRule, this.rulesBucketConstructionStateList, rulesBucketIndex);
5485
}
55-
56-
rulesBucket.AddRule(rule, specificRule, rulesBucketConstructionStateList, rulesBucketIndex);
5786
});
5887
});
5988
}
@@ -75,6 +104,11 @@ namespace ts.formatting {
75104
const MaskBitSize = 5;
76105
const Mask = 0x1f;
77106

107+
enum RulesAction {
108+
Add,
109+
Remove
110+
}
111+
78112
export enum RulesPosition {
79113
IgnoreRulesSpecific = 0,
80114
IgnoreRulesAny = MaskBitSize * 1,
@@ -121,10 +155,17 @@ namespace ts.formatting {
121155
return index;
122156
}
123157

124-
public IncreaseInsertionIndex(maskPosition: RulesPosition): void {
158+
public SetInsertionIndex(maskPosition: RulesPosition, action: RulesAction): void {
125159
let value = (this.rulesInsertionIndexBitmap >> maskPosition) & Mask;
126-
value++;
127-
Debug.assert((value & Mask) === value, "Adding more rules into the sub-bucket than allowed. Maximum allowed is 32 rules.");
160+
161+
if (action === RulesAction.Add) {
162+
value++;
163+
Debug.assert((value & Mask) === value, "Adding more rules into the sub-bucket than allowed. Maximum allowed is 32 rules.");
164+
}
165+
else {
166+
value--;
167+
Debug.assert(value >= 0, "Index should never be less than 0.");
168+
}
128169

129170
let temp = this.rulesInsertionIndexBitmap & ~(Mask << maskPosition);
130171
temp |= value << maskPosition;
@@ -145,6 +186,30 @@ namespace ts.formatting {
145186
}
146187

147188
public AddRule(rule: Rule, specificTokens: boolean, constructionState: RulesBucketConstructionState[], rulesBucketIndex: number): void {
189+
const position = this.GetMaskPosition(rule, specificTokens);
190+
let state = constructionState[rulesBucketIndex];
191+
if (state === undefined) {
192+
state = constructionState[rulesBucketIndex] = new RulesBucketConstructionState();
193+
}
194+
const index = state.GetInsertionIndex(position);
195+
this.rules.splice(index, 0, rule);
196+
state.SetInsertionIndex(position, RulesAction.Add);
197+
}
198+
199+
public RemoveRule(rule: Rule, specificTokens: boolean, constructionState: RulesBucketConstructionState[], rulesBucketIndex: number): void {
200+
const position = this.GetMaskPosition(rule, specificTokens);
201+
const state = constructionState[rulesBucketIndex];
202+
if (state === undefined) {
203+
return;
204+
}
205+
const index = this.rules.indexOf(rule);
206+
if (index > -1) {
207+
this.rules.splice(index, 1);
208+
state.SetInsertionIndex(position, RulesAction.Remove);
209+
}
210+
}
211+
212+
private GetMaskPosition(rule: Rule, specificTokens: boolean): RulesPosition {
148213
let position: RulesPosition;
149214

150215
if (rule.Operation.Action === RuleAction.Ignore) {
@@ -163,13 +228,7 @@ namespace ts.formatting {
163228
RulesPosition.NoContextRulesAny;
164229
}
165230

166-
let state = constructionState[rulesBucketIndex];
167-
if (state === undefined) {
168-
state = constructionState[rulesBucketIndex] = new RulesBucketConstructionState();
169-
}
170-
const index = state.GetInsertionIndex(position);
171-
this.rules.splice(index, 0, rule);
172-
state.IncreaseInsertionIndex(position);
231+
return position;
173232
}
174233
}
175234
}

src/services/formatting/rulesProvider.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ namespace ts.formatting {
55
export class RulesProvider {
66
private globalRules: Rules;
77
private options: ts.FormatCodeSettings;
8-
private activeRules: Rule[];
8+
private activeFormatOptionsRules: Rule[];
99
private rulesMap: RulesMap;
1010

1111
constructor() {
1212
this.globalRules = new Rules();
13+
14+
// Initialize the rulesMap with the high priority rules
15+
this.rulesMap = RulesMap.create(this.globalRules.HighPriorityCommonRules.slice(0), this.globalRules.LowPriorityCommonRules.slice(0));
16+
this.activeFormatOptionsRules = [];
1317
}
1418

1519
public getRuleName(rule: Rule): string {
@@ -26,17 +30,16 @@ namespace ts.formatting {
2630

2731
public ensureUpToDate(options: ts.FormatCodeSettings) {
2832
if (!this.options || !ts.compareDataObjects(this.options, options)) {
29-
const activeRules = this.createActiveRules(options);
30-
const rulesMap = RulesMap.create(activeRules);
33+
const newFormatOptionsRules = this.createFormatOptionsRules(options);
3134

32-
this.activeRules = activeRules;
33-
this.rulesMap = rulesMap;
35+
this.rulesMap.Update(this.activeFormatOptionsRules, newFormatOptionsRules);
36+
this.activeFormatOptionsRules = newFormatOptionsRules;
3437
this.options = ts.clone(options);
3538
}
3639
}
3740

38-
private createActiveRules(options: ts.FormatCodeSettings): Rule[] {
39-
let rules = this.globalRules.HighPriorityCommonRules.slice(0);
41+
private createFormatOptionsRules(options: ts.FormatCodeSettings): Rule[] {
42+
const rules = [];
4043

4144
if (options.insertSpaceAfterConstructor) {
4245
rules.push(this.globalRules.SpaceAfterConstructor);
@@ -158,8 +161,6 @@ namespace ts.formatting {
158161
rules.push(this.globalRules.NoSpaceAfterTypeAssertion);
159162
}
160163

161-
rules = rules.concat(this.globalRules.LowPriorityCommonRules);
162-
163164
return rules;
164165
}
165166
}

src/services/services.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ namespace ts {
3131
/** The version of the language service API */
3232
export const servicesVersion = "0.5";
3333

34+
const ruleProvider: formatting.RulesProvider = new formatting.RulesProvider();
35+
3436
function createNode<TKind extends SyntaxKind>(kind: TKind, pos: number, end: number, parent?: Node): NodeObject | TokenObject<TKind> | IdentifierObject {
3537
const node = kind >= SyntaxKind.FirstNode ? new NodeObject(kind, pos, end) :
3638
kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) :
@@ -973,10 +975,9 @@ namespace ts {
973975
}
974976

975977
export function createLanguageService(host: LanguageServiceHost,
976-
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory())): LanguageService {
978+
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()), formatOptions?: FormatCodeSettings): LanguageService {
977979

978980
const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
979-
let ruleProvider: formatting.RulesProvider;
980981
let program: Program;
981982
let lastProjectVersion: string;
982983

@@ -991,6 +992,11 @@ namespace ts {
991992
localizedDiagnosticMessages = host.getLocalizedDiagnosticMessages();
992993
}
993994

995+
// Update rules provider with code formatting options
996+
if (formatOptions) {
997+
ruleProvider.ensureUpToDate(formatOptions);
998+
}
999+
9941000
function log(message: string) {
9951001
if (host.log) {
9961002
host.log(message);
@@ -1008,11 +1014,7 @@ namespace ts {
10081014
}
10091015

10101016
function getRuleProvider(options: FormatCodeSettings) {
1011-
// Ensure rules are initialized and up to date wrt to formatting options
1012-
if (!ruleProvider) {
1013-
ruleProvider = new formatting.RulesProvider();
1014-
}
1015-
1017+
// Ensure rules are up to date wrt to formatting options
10161018
ruleProvider.ensureUpToDate(options);
10171019
return ruleProvider;
10181020
}

src/services/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,10 @@ namespace ts {
10491049
}
10501050

10511051
export function compareDataObjects(dst: any, src: any): boolean {
1052+
if (Object.keys(dst).length !== Object.keys(src).length) {
1053+
return false;
1054+
}
1055+
10521056
for (const e in dst) {
10531057
if (typeof dst[e] === "object") {
10541058
if (!compareDataObjects(dst[e], src[e])) {

0 commit comments

Comments
 (0)