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