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