• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import { Debug, every, FormatCodeSettings, FormattingHost, SyntaxKind } from "../_namespaces/ts";
2import {
3    anyContext, FormatContext, FormattingContext, getAllRules, Rule, RuleAction, RuleSpec,
4} from "../_namespaces/ts.formatting";
5
6/** @internal */
7export function getFormatContext(options: FormatCodeSettings, host: FormattingHost): FormatContext {
8    return { options, getRules: getRulesMap(), host };
9}
10
11let rulesMapCache: RulesMap | undefined;
12
13function getRulesMap(): RulesMap {
14    if (rulesMapCache === undefined) {
15        rulesMapCache = createRulesMap(getAllRules());
16    }
17    return rulesMapCache;
18}
19
20/**
21 * For a given rule action, gets a mask of other rule actions that
22 * cannot be applied at the same position.
23 */
24function getRuleActionExclusion(ruleAction: RuleAction): RuleAction {
25    let mask: RuleAction = 0;
26    if (ruleAction & RuleAction.StopProcessingSpaceActions) {
27        mask |= RuleAction.ModifySpaceAction;
28    }
29    if (ruleAction & RuleAction.StopProcessingTokenActions) {
30        mask |= RuleAction.ModifyTokenAction;
31    }
32    if (ruleAction & RuleAction.ModifySpaceAction) {
33        mask |= RuleAction.ModifySpaceAction;
34    }
35    if (ruleAction & RuleAction.ModifyTokenAction) {
36        mask |= RuleAction.ModifyTokenAction;
37    }
38    return mask;
39}
40
41/** @internal */
42export type RulesMap = (context: FormattingContext) => readonly Rule[] | undefined;
43function createRulesMap(rules: readonly RuleSpec[]): RulesMap {
44    const map = buildMap(rules);
45    return context => {
46        const bucket = map[getRuleBucketIndex(context.currentTokenSpan.kind, context.nextTokenSpan.kind)];
47        if (bucket) {
48            const rules: Rule[] = [];
49            let ruleActionMask: RuleAction = 0;
50            for (const rule of bucket) {
51                const acceptRuleActions = ~getRuleActionExclusion(ruleActionMask);
52                if (rule.action & acceptRuleActions && every(rule.context, c => c(context))) {
53                    rules.push(rule);
54                    ruleActionMask |= rule.action;
55                }
56            }
57            if (rules.length) {
58                return rules;
59            }
60        }
61    };
62}
63
64function buildMap(rules: readonly RuleSpec[]): readonly (readonly Rule[])[] {
65    // Map from bucket index to array of rules
66    const map: Rule[][] = new Array(mapRowLength * mapRowLength);
67    // This array is used only during construction of the rulesbucket in the map
68    const rulesBucketConstructionStateList = new Array<number>(map.length);
69    for (const rule of rules) {
70        const specificRule = rule.leftTokenRange.isSpecific && rule.rightTokenRange.isSpecific;
71
72        for (const left of rule.leftTokenRange.tokens) {
73            for (const right of rule.rightTokenRange.tokens) {
74                const index = getRuleBucketIndex(left, right);
75                let rulesBucket = map[index];
76                if (rulesBucket === undefined) {
77                    rulesBucket = map[index] = [];
78                }
79                addRule(rulesBucket, rule.rule, specificRule, rulesBucketConstructionStateList, index);
80            }
81        }
82    }
83    return map;
84}
85
86function getRuleBucketIndex(row: number, column: number): number {
87    Debug.assert(row <= SyntaxKind.LastKeyword && column <= SyntaxKind.LastKeyword, "Must compute formatting context from tokens");
88    return (row * mapRowLength) + column;
89}
90
91const maskBitSize = 5;
92const mask = 0b11111; // MaskBitSize bits
93const mapRowLength = SyntaxKind.LastToken + 1;
94
95enum RulesPosition {
96    StopRulesSpecific = 0,
97    StopRulesAny = maskBitSize * 1,
98    ContextRulesSpecific = maskBitSize * 2,
99    ContextRulesAny = maskBitSize * 3,
100    NoContextRulesSpecific = maskBitSize * 4,
101    NoContextRulesAny = maskBitSize * 5
102}
103
104// The Rules list contains all the inserted rules into a rulebucket in the following order:
105//    1- Ignore rules with specific token combination
106//    2- Ignore rules with any token combination
107//    3- Context rules with specific token combination
108//    4- Context rules with any token combination
109//    5- Non-context rules with specific token combination
110//    6- Non-context rules with any token combination
111//
112// The member rulesInsertionIndexBitmap is used to describe the number of rules
113// in each sub-bucket (above) hence can be used to know the index of where to insert
114// the next rule. It's a bitmap which contains 6 different sections each is given 5 bits.
115//
116// Example:
117// In order to insert a rule to the end of sub-bucket (3), we get the index by adding
118// the values in the bitmap segments 3rd, 2nd, and 1st.
119function addRule(rules: Rule[], rule: Rule, specificTokens: boolean, constructionState: number[], rulesBucketIndex: number): void {
120    const position = rule.action & RuleAction.StopAction ?
121        specificTokens ? RulesPosition.StopRulesSpecific : RulesPosition.StopRulesAny :
122        rule.context !== anyContext ?
123            specificTokens ? RulesPosition.ContextRulesSpecific : RulesPosition.ContextRulesAny :
124            specificTokens ? RulesPosition.NoContextRulesSpecific : RulesPosition.NoContextRulesAny;
125
126    const state = constructionState[rulesBucketIndex] || 0;
127    rules.splice(getInsertionIndex(state, position), 0, rule);
128    constructionState[rulesBucketIndex] = increaseInsertionIndex(state, position);
129}
130
131function getInsertionIndex(indexBitmap: number, maskPosition: RulesPosition) {
132    let index = 0;
133    for (let pos = 0; pos <= maskPosition; pos += maskBitSize) {
134        index += indexBitmap & mask;
135        indexBitmap >>= maskBitSize;
136    }
137    return index;
138}
139
140function increaseInsertionIndex(indexBitmap: number, maskPosition: RulesPosition): number {
141    const value = ((indexBitmap >> maskPosition) & mask) + 1;
142    Debug.assert((value & mask) === value, "Adding more rules into the sub-bucket than allowed. Maximum allowed is 32 rules.");
143    return (indexBitmap & ~(mask << maskPosition)) | (value << maskPosition);
144}
145