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