diff --git a/src/FSharpLint.Core/FSharpLint.Core.fsproj b/src/FSharpLint.Core/FSharpLint.Core.fsproj index 05f0be817..7d7c8dd0f 100644 --- a/src/FSharpLint.Core/FSharpLint.Core.fsproj +++ b/src/FSharpLint.Core/FSharpLint.Core.fsproj @@ -17,6 +17,8 @@ + + diff --git a/src/FSharpLint.Core/Framework/HintParser.fs b/src/FSharpLint.Core/Framework/HintParser.fs index 1cc333881..672cb616f 100644 --- a/src/FSharpLint.Core/Framework/HintParser.fs +++ b/src/FSharpLint.Core/Framework/HintParser.fs @@ -3,389 +3,10 @@ open System open FParsec open FSharp.Compiler.Tokenization +open HintParserTypes module HintParser = - type Constant = - | Byte of byte - | Bytes of byte[] - | Char of char - | Decimal of decimal - | Double of double - | Int16 of int16 - | Int32 of int32 - | Int64 of int64 - | IntPtr of nativeint - | SByte of sbyte - | Single of single - | UInt16 of uint16 - | UInt32 of uint32 - | UInt64 of uint64 - | UIntPtr of unativeint - | UserNum of bigint * char - | String of string - | Unit - | Bool of bool - - [] - type Pattern = - | Cons of Pattern * Pattern - | Or of Pattern * Pattern - | Wildcard - | Variable of char - | Identifier of string list - | Constant of Constant - | Parentheses of Pattern - | Tuple of Pattern list - | List of Pattern list - | Array of Pattern list - | Null - - [] - type Expression = - | FunctionApplication of Expression list - | InfixOperator of operatorIdentifier:Expression * Expression * Expression - | PrefixOperator of operatorIdentifier:Expression * Expression - | AddressOf of singleAmpersand:bool * Expression - | Wildcard - | Variable of char - | Identifier of string list - | Constant of Constant - | Parentheses of Expression - | Lambda of LambdaArg list * LambdaBody - | LambdaBody of Expression - | LambdaArg of Expression - | Tuple of Expression list - | List of Expression list - | Array of Expression list - | If of cond:Expression * body:Expression * ``else``:Expression option - | Else of Expression - | Null - and LambdaArg = LambdaArg of Expression - and LambdaBody = LambdaBody of Expression - - type HintNode = - | HintPat of Pattern - | HintExpr of Expression - - type Suggestion = - | Expr of Expression - | Message of string - - type Hint = - { MatchedNode:HintNode - Suggestion:Suggestion } - - /// Provides a way of creating a single list from any number of hint ASTs. - /// Means we can simply iterate over a single list for each node in the F# tree - /// when matching hints rather than check each hint AST for each node. - module MergeSyntaxTrees = - - open System.Collections.Generic - - type SyntaxHintNode = - | Identifier = 1uy - | Null = 2uy - | Expression = 3uy - | FuncApp = 4uy - | Unit = 5uy - | AddressOf = 6uy - - | If = 10uy - | Else = 11uy - - | Lambda = 20uy - | LambdaArg = 21uy - | LambdaBody = 22uy - - | ArrayOrList = 30uy - | Tuple = 31uy - - | Variable = 40uy - | Wildcard = 41uy - - | ConstantBool = 51uy - | ConstantByte = 52uy - | ConstantChar = 53uy - | ConstantDecimal = 54uy - | ConstantDouble = 55uy - | ConstantInt16 = 56uy - | ConstantInt32 = 57uy - | ConstantInt64 = 58uy - | ConstantIntPtr = 59uy - | ConstantSByte = 60uy - | ConstantSingle = 61uy - | ConstantString = 62uy - | ConstantUInt16 = 63uy - | ConstantUInt32 = 64uy - | ConstantUInt64 = 65uy - | ConstantUIntPtr = 66uy - | ConstantBytes = 67uy - | ConstantUserNum = 68uy - - | Cons = 101uy - | And = 102uy - | Or = 103uy - - [] - type Node = - { Edges:Edges - MatchedHint:Hint list } - and [] Edges = - { Lookup:Dictionary - AnyMatch:(char option * Node) list } - - override this.Equals(other) = - match other with - | :? Edges as rhs -> - let getList dict = Seq.toList dict |> List.map (fun (dictItems:KeyValuePair<_, _>) -> (dictItems.Key, dictItems.Value)) - - this.AnyMatch = rhs.AnyMatch && - this.Lookup.Count = rhs.Lookup.Count && - getList this.Lookup = getList rhs.Lookup - | _ -> false - - override this.GetHashCode() = hash (this.AnyMatch, hash this.Lookup) - - static member Empty = { Lookup = Dictionary<_, _>(); AnyMatch = List.Empty } - - let private getConstKey = function - | Constant.Unit -> SyntaxHintNode.Unit - | Constant.Bool(_) -> SyntaxHintNode.ConstantBool - | Constant.Byte(_) -> SyntaxHintNode.ConstantByte - | Constant.Bytes(_) -> SyntaxHintNode.ConstantBytes - | Constant.Char(_) -> SyntaxHintNode.ConstantChar - | Constant.Decimal(_) -> SyntaxHintNode.ConstantDecimal - | Constant.Double(_) -> SyntaxHintNode.ConstantDouble - | Constant.Int16(_) -> SyntaxHintNode.ConstantInt16 - | Constant.Int32(_) -> SyntaxHintNode.ConstantInt32 - | Constant.Int64(_) -> SyntaxHintNode.ConstantInt64 - | Constant.IntPtr(_) -> SyntaxHintNode.ConstantIntPtr - | Constant.SByte(_) -> SyntaxHintNode.ConstantSByte - | Constant.Single(_) -> SyntaxHintNode.ConstantSingle - | Constant.String(_) -> SyntaxHintNode.ConstantString - | Constant.UInt16(_) -> SyntaxHintNode.ConstantUInt16 - | Constant.UInt32(_) -> SyntaxHintNode.ConstantUInt32 - | Constant.UInt64(_) -> SyntaxHintNode.ConstantUInt64 - | Constant.UIntPtr(_) -> SyntaxHintNode.ConstantUIntPtr - | Constant.UserNum(_) -> SyntaxHintNode.ConstantUserNum - - let rec private getExprKey = function - | Expression.FunctionApplication(_) - | Expression.InfixOperator(_) - | Expression.PrefixOperator(_) -> SyntaxHintNode.FuncApp - | Expression.AddressOf(_) -> SyntaxHintNode.AddressOf - | Expression.Parentheses(expr) -> getExprKey expr - | Expression.Lambda(_) -> SyntaxHintNode.Lambda - | Expression.LambdaArg(_) -> SyntaxHintNode.LambdaArg - | Expression.LambdaBody(_) -> SyntaxHintNode.LambdaBody - | Expression.Tuple(_) -> SyntaxHintNode.Tuple - | Expression.Constant(constant) -> getConstKey constant - | Expression.List(_) - | Expression.Array(_) -> SyntaxHintNode.ArrayOrList - | Expression.If(_) -> SyntaxHintNode.If - | Expression.Else(_) -> SyntaxHintNode.Else - | Expression.Identifier(_) -> SyntaxHintNode.Identifier - | Expression.Null -> SyntaxHintNode.Null - | Expression.Wildcard -> SyntaxHintNode.Wildcard - | Expression.Variable(_) -> SyntaxHintNode.Variable - - let rec private getPatternKey = function - | Pattern.Cons(_) -> SyntaxHintNode.Cons - | Pattern.Or(_) -> SyntaxHintNode.Or - | Pattern.Wildcard -> SyntaxHintNode.Wildcard - | Pattern.Variable(_) -> SyntaxHintNode.Variable - | Pattern.Identifier(_) -> SyntaxHintNode.Identifier - | Pattern.Constant(constant) -> getConstKey constant - | Pattern.Parentheses(pattern) -> getPatternKey pattern - | Pattern.Tuple(_) -> SyntaxHintNode.Tuple - | Pattern.List(_) - | Pattern.Array(_) -> SyntaxHintNode.ArrayOrList - | Pattern.Null -> SyntaxHintNode.Null - - let rec private getKey = function - | HintExpr(expr) -> getExprKey expr - | HintPat(pattern) -> getPatternKey pattern - - let rec private getChildren = function - | HintExpr(Expression.Parentheses(expr)) -> getChildren <| HintExpr expr - | HintExpr(Expression.Lambda(args, LambdaBody(body))) -> - [ for LambdaArg(arg) in args -> HintExpr arg - yield HintExpr body ] - | HintExpr(Expression.LambdaArg(arg)) -> - [HintExpr arg] - | HintExpr(Expression.LambdaBody(body)) -> - [HintExpr body] - | HintExpr(Expression.InfixOperator(Expression.Identifier(["::"]) as ident, lhs, rhs)) -> - [HintExpr ident; HintExpr (Expression.Tuple([lhs; rhs]))] - | HintExpr(Expression.InfixOperator(ident, lhs, rhs)) -> - [HintExpr ident; HintExpr lhs; HintExpr rhs] - | HintExpr(Expression.PrefixOperator(ident, expr)) -> - [HintExpr ident; HintExpr expr] - | HintExpr(Expression.AddressOf(_, expr)) -> [HintExpr expr] - | HintExpr(Expression.FunctionApplication(exprs)) - | HintExpr(Expression.Tuple(exprs)) - | HintExpr(Expression.List(exprs)) - | HintExpr(Expression.Array(exprs)) -> exprs |> List.map HintExpr - | HintExpr(Expression.If(ifCond, bodyExpr, Some(elseExpr))) -> - [HintExpr ifCond; HintExpr bodyExpr; HintExpr elseExpr] - | HintExpr(Expression.If(ifCond, bodyExpr, None)) -> - [HintExpr ifCond; HintExpr bodyExpr] - | HintExpr(Expression.Else(expression)) -> [HintExpr expression] - | HintExpr(Expression.Identifier(_)) - | HintExpr(Expression.Constant(_)) - | HintExpr(Expression.Null) - | HintExpr(Expression.Wildcard) - | HintExpr(Expression.Variable(_)) -> List.Empty - | HintPat(Pattern.Cons(lhs, rhs)) - | HintPat(Pattern.Or(lhs, rhs)) -> [HintPat lhs; HintPat rhs] - | HintPat(Pattern.Array(patterns)) - | HintPat(Pattern.List(patterns)) - | HintPat(Pattern.Tuple(patterns)) -> patterns |> List.map HintPat - | HintPat(Pattern.Parentheses(pattern)) -> [HintPat pattern] - | HintPat(Pattern.Variable(_)) - | HintPat(Pattern.Identifier(_)) - | HintPat(Pattern.Constant(_)) - | HintPat(Pattern.Wildcard) - | HintPat(Pattern.Null) -> List.Empty - - let private getConstantHashCode = function - | Constant.Bool value -> hash value - | Constant.Byte value -> hash value - | Constant.Bytes value -> hash value - | Constant.Char value -> hash value - | Constant.Decimal value -> hash value - | Constant.Double value -> hash value - | Constant.Int16 value -> hash value - | Constant.Int32 value -> hash value - | Constant.Int64 value -> hash value - | Constant.IntPtr value -> hash value - | Constant.SByte value -> hash value - | Constant.Single value -> hash value - | Constant.String value -> hash value - | Constant.UInt16 value -> hash value - | Constant.UInt32 value -> hash value - | Constant.UInt64 value -> hash value - | Constant.UIntPtr value -> hash value - | Constant.UserNum(intValue, charValue) -> hash (intValue, charValue) - | _ -> 0 - - let private getIdentifierHashCode = function - | identifier when (List.isEmpty >> not) identifier -> - match (Seq.tryLast identifier) with - | Some value -> value |> ExpressionUtilities.identAsCompiledOpName |> hash - | None -> failwith "There's no last element in identifier." - | _ -> 0 - - let rec private getHashCode node = - match node with - | HintExpr(Expression.Identifier(identifier)) - | HintPat(Pattern.Identifier(identifier)) -> getIdentifierHashCode identifier - | HintExpr(Expression.Constant(constant)) - | HintPat(Pattern.Constant(constant)) -> getConstantHashCode constant - | HintExpr(Expression.Parentheses(expr)) -> getHashCode <| HintExpr expr - | HintPat(Pattern.Parentheses(expr)) -> getHashCode <| HintPat expr - | _ -> 0 - - let private hintToList (hint:Hint) = - let nodes = Queue<_>() - - let rec depthFirstTraversal expr depth = - let children = getChildren expr - - nodes.Enqueue(expr, depth) - - for child in children do - depthFirstTraversal child (depth + 1) - - depthFirstTraversal hint.MatchedNode 0 - - (nodes |> Seq.toList, hint) - - type private HintList = (HintNode * int) list * Hint - - type private TransposedNode = - | HintNode of key:HintNode * depth:int * rest:HintList - | EndOfHint of Hint - - /// Gets the head of each given list - let private transposeHead hintLists = - let rec transposeHead builtList = function - | (((key, depth)::tail), hint)::rest -> - let restOfHintList = (tail, hint) - let next = HintNode(key, depth, restOfHintList)::builtList - transposeHead next rest - | ([], hint)::rest -> - let next = EndOfHint(hint)::builtList - transposeHead next rest - | [] -> builtList - - transposeHead List.Empty hintLists - - let isAnyMatch = function - | ((SyntaxHintNode.Wildcard | SyntaxHintNode.Variable), _, _, _) -> true - | _ -> false - - let getHints items = items |> Seq.map (fun (_, _, _, hint) -> hint) |> Seq.toList - - let mergeHints hints = - let rec getEdges transposed = - let map = Dictionary<_, _>() - - transposed - |> List.choose (function - | HintNode(expr, depth, rest) -> Some(getKey expr, expr, depth, rest) - | EndOfHint(_) -> None) - |> List.filter (isAnyMatch >> not) - |> Seq.groupBy (fun (key, expr, _, _) -> Utilities.hash2 key (getHashCode expr)) - |> Seq.iter (fun (hashcode, items) -> map.Add(hashcode, mergeHints (getHints items))) - - let anyMatches = - transposed - |> List.choose (function - | HintNode(expr, depth, rest) -> - match (getKey expr, expr) with - | (SyntaxHintNode.Wildcard as key), HintExpr(Expression.Wildcard) - | (SyntaxHintNode.Wildcard as key), HintPat(Pattern.Wildcard) - | (SyntaxHintNode.Variable as key), HintExpr(Expression.Variable(_)) - | (SyntaxHintNode.Variable as key), HintPat(Pattern.Variable(_)) -> - Some(key, expr, depth, rest) - | _ -> None - | EndOfHint(_) -> None) - |> Seq.groupBy (fun (_, expr, _, _) -> expr) - |> Seq.choose - (fun (expr, items) -> - match expr with - | HintPat(Pattern.Wildcard) - | HintExpr(Expression.Wildcard) -> Some(None, mergeHints (getHints items)) - | HintPat(Pattern.Variable(var)) - | HintExpr(Expression.Variable(var)) -> Some(Some(var), mergeHints (getHints items)) - | _ -> None) - |> Seq.toList - - { Lookup = map - AnyMatch = anyMatches } - - and mergeHints hints = - let transposed = transposeHead hints - - let edges = getEdges transposed - - let matchedHints = - transposed - |> Seq.choose (function - | HintNode(_) -> None - | EndOfHint(hint) -> Some(hint)) - |> Seq.toList - - { Edges = edges - MatchedHint = matchedHints } - - let transposed = - hints |> List.map hintToList |> transposeHead - - getEdges transposed - let charListToString charList = Seq.fold (fun concatenatedString charElement -> concatenatedString + charElement.ToString()) String.Empty charList diff --git a/src/FSharpLint.Core/Framework/HintParserTypes.fs b/src/FSharpLint.Core/Framework/HintParserTypes.fs new file mode 100644 index 000000000..5ba73a328 --- /dev/null +++ b/src/FSharpLint.Core/Framework/HintParserTypes.fs @@ -0,0 +1,73 @@ +namespace FSharpLint.Framework + +module HintParserTypes = + + type Constant = + | Byte of byte + | Bytes of byte[] + | Char of char + | Decimal of decimal + | Double of double + | Int16 of int16 + | Int32 of int32 + | Int64 of int64 + | IntPtr of nativeint + | SByte of sbyte + | Single of single + | UInt16 of uint16 + | UInt32 of uint32 + | UInt64 of uint64 + | UIntPtr of unativeint + | UserNum of bigint * char + | String of string + | Unit + | Bool of bool + + [] + type Pattern = + | Cons of Pattern * Pattern + | Or of Pattern * Pattern + | Wildcard + | Variable of char + | Identifier of string list + | Constant of Constant + | Parentheses of Pattern + | Tuple of Pattern list + | List of Pattern list + | Array of Pattern list + | Null + + [] + type Expression = + | FunctionApplication of Expression list + | InfixOperator of operatorIdentifier:Expression * Expression * Expression + | PrefixOperator of operatorIdentifier:Expression * Expression + | AddressOf of singleAmpersand:bool * Expression + | Wildcard + | Variable of char + | Identifier of string list + | Constant of Constant + | Parentheses of Expression + | Lambda of LambdaArg list * LambdaBody + | LambdaBody of Expression + | LambdaArg of Expression + | Tuple of Expression list + | List of Expression list + | Array of Expression list + | If of cond:Expression * body:Expression * ``else``:Expression option + | Else of Expression + | Null + and LambdaArg = LambdaArg of Expression + and LambdaBody = LambdaBody of Expression + + type HintNode = + | HintPat of Pattern + | HintExpr of Expression + + type Suggestion = + | Expr of Expression + | Message of string + + type Hint = + { MatchedNode:HintNode + Suggestion:Suggestion } \ No newline at end of file diff --git a/src/FSharpLint.Core/Framework/HintParserUtilities.fs b/src/FSharpLint.Core/Framework/HintParserUtilities.fs new file mode 100644 index 000000000..0ef15015f --- /dev/null +++ b/src/FSharpLint.Core/Framework/HintParserUtilities.fs @@ -0,0 +1,313 @@ +namespace FSharpLint.Framework + +open HintParserTypes + +/// Provides a way of creating a single list from any number of hint ASTs. +/// Means we can simply iterate over a single list for each node in the F# tree +/// when matching hints rather than check each hint AST for each node. +module MergeSyntaxTrees = + + open System.Collections.Generic + + type SyntaxHintNode = + | Identifier = 1uy + | Null = 2uy + | Expression = 3uy + | FuncApp = 4uy + | Unit = 5uy + | AddressOf = 6uy + + | If = 10uy + | Else = 11uy + + | Lambda = 20uy + | LambdaArg = 21uy + | LambdaBody = 22uy + + | ArrayOrList = 30uy + | Tuple = 31uy + + | Variable = 40uy + | Wildcard = 41uy + + | ConstantBool = 51uy + | ConstantByte = 52uy + | ConstantChar = 53uy + | ConstantDecimal = 54uy + | ConstantDouble = 55uy + | ConstantInt16 = 56uy + | ConstantInt32 = 57uy + | ConstantInt64 = 58uy + | ConstantIntPtr = 59uy + | ConstantSByte = 60uy + | ConstantSingle = 61uy + | ConstantString = 62uy + | ConstantUInt16 = 63uy + | ConstantUInt32 = 64uy + | ConstantUInt64 = 65uy + | ConstantUIntPtr = 66uy + | ConstantBytes = 67uy + | ConstantUserNum = 68uy + + | Cons = 101uy + | And = 102uy + | Or = 103uy + + [] + type Node = + { Edges:Edges + MatchedHint:Hint list } + and [] Edges = + { Lookup:Dictionary + AnyMatch:(char option * Node) list } + + override this.Equals(other) = + match other with + | :? Edges as rhs -> + let getList dict = Seq.toList dict |> List.map (fun (dictItems:KeyValuePair<_, _>) -> (dictItems.Key, dictItems.Value)) + + this.AnyMatch = rhs.AnyMatch && + this.Lookup.Count = rhs.Lookup.Count && + getList this.Lookup = getList rhs.Lookup + | _ -> false + + override this.GetHashCode() = hash (this.AnyMatch, hash this.Lookup) + + static member Empty = { Lookup = Dictionary<_, _>(); AnyMatch = List.Empty } + + let private getConstKey = function + | Constant.Unit -> SyntaxHintNode.Unit + | Constant.Bool(_) -> SyntaxHintNode.ConstantBool + | Constant.Byte(_) -> SyntaxHintNode.ConstantByte + | Constant.Bytes(_) -> SyntaxHintNode.ConstantBytes + | Constant.Char(_) -> SyntaxHintNode.ConstantChar + | Constant.Decimal(_) -> SyntaxHintNode.ConstantDecimal + | Constant.Double(_) -> SyntaxHintNode.ConstantDouble + | Constant.Int16(_) -> SyntaxHintNode.ConstantInt16 + | Constant.Int32(_) -> SyntaxHintNode.ConstantInt32 + | Constant.Int64(_) -> SyntaxHintNode.ConstantInt64 + | Constant.IntPtr(_) -> SyntaxHintNode.ConstantIntPtr + | Constant.SByte(_) -> SyntaxHintNode.ConstantSByte + | Constant.Single(_) -> SyntaxHintNode.ConstantSingle + | Constant.String(_) -> SyntaxHintNode.ConstantString + | Constant.UInt16(_) -> SyntaxHintNode.ConstantUInt16 + | Constant.UInt32(_) -> SyntaxHintNode.ConstantUInt32 + | Constant.UInt64(_) -> SyntaxHintNode.ConstantUInt64 + | Constant.UIntPtr(_) -> SyntaxHintNode.ConstantUIntPtr + | Constant.UserNum(_) -> SyntaxHintNode.ConstantUserNum + + let rec private getExprKey = function + | Expression.FunctionApplication(_) + | Expression.InfixOperator(_) + | Expression.PrefixOperator(_) -> SyntaxHintNode.FuncApp + | Expression.AddressOf(_) -> SyntaxHintNode.AddressOf + | Expression.Parentheses(expr) -> getExprKey expr + | Expression.Lambda(_) -> SyntaxHintNode.Lambda + | Expression.LambdaArg(_) -> SyntaxHintNode.LambdaArg + | Expression.LambdaBody(_) -> SyntaxHintNode.LambdaBody + | Expression.Tuple(_) -> SyntaxHintNode.Tuple + | Expression.Constant(constant) -> getConstKey constant + | Expression.List(_) + | Expression.Array(_) -> SyntaxHintNode.ArrayOrList + | Expression.If(_) -> SyntaxHintNode.If + | Expression.Else(_) -> SyntaxHintNode.Else + | Expression.Identifier(_) -> SyntaxHintNode.Identifier + | Expression.Null -> SyntaxHintNode.Null + | Expression.Wildcard -> SyntaxHintNode.Wildcard + | Expression.Variable(_) -> SyntaxHintNode.Variable + + let rec private getPatternKey = function + | Pattern.Cons(_) -> SyntaxHintNode.Cons + | Pattern.Or(_) -> SyntaxHintNode.Or + | Pattern.Wildcard -> SyntaxHintNode.Wildcard + | Pattern.Variable(_) -> SyntaxHintNode.Variable + | Pattern.Identifier(_) -> SyntaxHintNode.Identifier + | Pattern.Constant(constant) -> getConstKey constant + | Pattern.Parentheses(pattern) -> getPatternKey pattern + | Pattern.Tuple(_) -> SyntaxHintNode.Tuple + | Pattern.List(_) + | Pattern.Array(_) -> SyntaxHintNode.ArrayOrList + | Pattern.Null -> SyntaxHintNode.Null + + let rec private getKey = function + | HintExpr(expr) -> getExprKey expr + | HintPat(pattern) -> getPatternKey pattern + + let rec private getChildren = function + | HintExpr(Expression.Parentheses(expr)) -> getChildren <| HintExpr expr + | HintExpr(Expression.Lambda(args, LambdaBody(body))) -> + [ for LambdaArg(arg) in args -> HintExpr arg + yield HintExpr body ] + | HintExpr(Expression.LambdaArg(arg)) -> + [HintExpr arg] + | HintExpr(Expression.LambdaBody(body)) -> + [HintExpr body] + | HintExpr(Expression.InfixOperator(Expression.Identifier(["::"]) as ident, lhs, rhs)) -> + [HintExpr ident; HintExpr (Expression.Tuple([lhs; rhs]))] + | HintExpr(Expression.InfixOperator(ident, lhs, rhs)) -> + [HintExpr ident; HintExpr lhs; HintExpr rhs] + | HintExpr(Expression.PrefixOperator(ident, expr)) -> + [HintExpr ident; HintExpr expr] + | HintExpr(Expression.AddressOf(_, expr)) -> [HintExpr expr] + | HintExpr(Expression.FunctionApplication(exprs)) + | HintExpr(Expression.Tuple(exprs)) + | HintExpr(Expression.List(exprs)) + | HintExpr(Expression.Array(exprs)) -> exprs |> List.map HintExpr + | HintExpr(Expression.If(ifCond, bodyExpr, Some(elseExpr))) -> + [HintExpr ifCond; HintExpr bodyExpr; HintExpr elseExpr] + | HintExpr(Expression.If(ifCond, bodyExpr, None)) -> + [HintExpr ifCond; HintExpr bodyExpr] + | HintExpr(Expression.Else(expression)) -> [HintExpr expression] + | HintExpr(Expression.Identifier(_)) + | HintExpr(Expression.Constant(_)) + | HintExpr(Expression.Null) + | HintExpr(Expression.Wildcard) + | HintExpr(Expression.Variable(_)) -> List.Empty + | HintPat(Pattern.Cons(lhs, rhs)) + | HintPat(Pattern.Or(lhs, rhs)) -> [HintPat lhs; HintPat rhs] + | HintPat(Pattern.Array(patterns)) + | HintPat(Pattern.List(patterns)) + | HintPat(Pattern.Tuple(patterns)) -> patterns |> List.map HintPat + | HintPat(Pattern.Parentheses(pattern)) -> [HintPat pattern] + | HintPat(Pattern.Variable(_)) + | HintPat(Pattern.Identifier(_)) + | HintPat(Pattern.Constant(_)) + | HintPat(Pattern.Wildcard) + | HintPat(Pattern.Null) -> List.Empty + + let private getConstantHashCode = function + | Constant.Bool value -> hash value + | Constant.Byte value -> hash value + | Constant.Bytes value -> hash value + | Constant.Char value -> hash value + | Constant.Decimal value -> hash value + | Constant.Double value -> hash value + | Constant.Int16 value -> hash value + | Constant.Int32 value -> hash value + | Constant.Int64 value -> hash value + | Constant.IntPtr value -> hash value + | Constant.SByte value -> hash value + | Constant.Single value -> hash value + | Constant.String value -> hash value + | Constant.UInt16 value -> hash value + | Constant.UInt32 value -> hash value + | Constant.UInt64 value -> hash value + | Constant.UIntPtr value -> hash value + | Constant.UserNum(intValue, charValue) -> hash (intValue, charValue) + | _ -> 0 + + let private getIdentifierHashCode = function + | identifier when (List.isEmpty >> not) identifier -> + match (Seq.tryLast identifier) with + | Some value -> value |> ExpressionUtilities.identAsCompiledOpName |> hash + | None -> failwith "There's no last element in identifier." + | _ -> 0 + + let rec private getHashCode node = + match node with + | HintExpr(Expression.Identifier(identifier)) + | HintPat(Pattern.Identifier(identifier)) -> getIdentifierHashCode identifier + | HintExpr(Expression.Constant(constant)) + | HintPat(Pattern.Constant(constant)) -> getConstantHashCode constant + | HintExpr(Expression.Parentheses(expr)) -> getHashCode <| HintExpr expr + | HintPat(Pattern.Parentheses(expr)) -> getHashCode <| HintPat expr + | _ -> 0 + + let private hintToList (hint:Hint) = + let nodes = Queue<_>() + + let rec depthFirstTraversal expr depth = + let children = getChildren expr + + nodes.Enqueue(expr, depth) + + for child in children do + depthFirstTraversal child (depth + 1) + + depthFirstTraversal hint.MatchedNode 0 + + (nodes |> Seq.toList, hint) + + type private HintList = (HintNode * int) list * Hint + + type private TransposedNode = + | HintNode of key:HintNode * depth:int * rest:HintList + | EndOfHint of Hint + + /// Gets the head of each given list + let private transposeHead hintLists = + let rec transposeHead builtList = function + | (((key, depth)::tail), hint)::rest -> + let restOfHintList = (tail, hint) + let next = HintNode(key, depth, restOfHintList)::builtList + transposeHead next rest + | ([], hint)::rest -> + let next = EndOfHint(hint)::builtList + transposeHead next rest + | [] -> builtList + + transposeHead List.Empty hintLists + + let isAnyMatch = function + | ((SyntaxHintNode.Wildcard | SyntaxHintNode.Variable), _, _, _) -> true + | _ -> false + + let getHints items = items |> Seq.map (fun (_, _, _, hint) -> hint) |> Seq.toList + + let mergeHints hints = + let rec getEdges transposed = + let map = Dictionary<_, _>() + + transposed + |> List.choose (function + | HintNode(expr, depth, rest) -> Some(getKey expr, expr, depth, rest) + | EndOfHint(_) -> None) + |> List.filter (isAnyMatch >> not) + |> Seq.groupBy (fun (key, expr, _, _) -> Utilities.hash2 key (getHashCode expr)) + |> Seq.iter (fun (hashcode, items) -> map.Add(hashcode, mergeHints (getHints items))) + + let anyMatches = + transposed + |> List.choose (function + | HintNode(expr, depth, rest) -> + match (getKey expr, expr) with + | (SyntaxHintNode.Wildcard as key), HintExpr(Expression.Wildcard) + | (SyntaxHintNode.Wildcard as key), HintPat(Pattern.Wildcard) + | (SyntaxHintNode.Variable as key), HintExpr(Expression.Variable(_)) + | (SyntaxHintNode.Variable as key), HintPat(Pattern.Variable(_)) -> + Some(key, expr, depth, rest) + | _ -> None + | EndOfHint(_) -> None) + |> Seq.groupBy (fun (_, expr, _, _) -> expr) + |> Seq.choose + (fun (expr, items) -> + match expr with + | HintPat(Pattern.Wildcard) + | HintExpr(Expression.Wildcard) -> Some(None, mergeHints (getHints items)) + | HintPat(Pattern.Variable(var)) + | HintExpr(Expression.Variable(var)) -> Some(Some(var), mergeHints (getHints items)) + | _ -> None) + |> Seq.toList + + { Lookup = map + AnyMatch = anyMatches } + + and mergeHints hints = + let transposed = transposeHead hints + + let edges = getEdges transposed + + let matchedHints = + transposed + |> Seq.choose (function + | HintNode(_) -> None + | EndOfHint(hint) -> Some(hint)) + |> Seq.toList + + { Edges = edges + MatchedHint = matchedHints } + + let transposed = + hints |> List.map hintToList |> transposeHead + + getEdges transposed diff --git a/src/FSharpLint.Core/Rules/Hints/HintMatcher.fs b/src/FSharpLint.Core/Rules/Hints/HintMatcher.fs index b8cfe2127..de0f9fbca 100644 --- a/src/FSharpLint.Core/Rules/Hints/HintMatcher.fs +++ b/src/FSharpLint.Core/Rules/Hints/HintMatcher.fs @@ -12,6 +12,7 @@ open FSharpLint.Framework.Ast open FSharpLint.Framework.ExpressionUtilities open FSharpLint.Framework.HintParser open FSharpLint.Framework.Rules +open FSharpLint.Framework.HintParserTypes type ToStringConfig = { @@ -62,7 +63,7 @@ type private LambdaMatch = | Match of Map | NoMatch -let private matchLambdaArguments (hintArgs:HintParser.LambdaArg list) (actualArgs:SynSimplePats list) = +let private matchLambdaArguments (hintArgs:HintParserTypes.LambdaArg list) (actualArgs:SynSimplePats list) = if List.length hintArgs <> List.length actualArgs then LambdaMatch.NoMatch else @@ -704,7 +705,7 @@ let private (|SuggestingReplacementOfLambda|OtherSuggestion|) = function let [] private MaxBreadcrumbs = 6 let private suggestions = ResizeArray() -let private confirmFuzzyMatch (args:AstNodeRuleParams) (hint:HintParser.Hint) = +let private confirmFuzzyMatch (args:AstNodeRuleParams) (hint:HintParserTypes.Hint) = let breadcrumbs = args.GetParents MaxBreadcrumbs match (args.AstNode, hint.MatchedNode) with | AstNode.Expression(SynExpr.Paren(_)), HintExpr(_) diff --git a/tests/FSharpLint.Core.Tests/Framework/TestFuzzyHintMatcher.fs b/tests/FSharpLint.Core.Tests/Framework/TestFuzzyHintMatcher.fs index 3a539ddae..dd32c617f 100644 --- a/tests/FSharpLint.Core.Tests/Framework/TestFuzzyHintMatcher.fs +++ b/tests/FSharpLint.Core.Tests/Framework/TestFuzzyHintMatcher.fs @@ -6,7 +6,7 @@ open FSharpLint.Rules.Helper.Hints open FSharpLint.Framework open FSharpLint.Framework.AbstractSyntaxArray open FSharpLint.Framework.HintParser -open FSharpLint.Framework.HintParser.MergeSyntaxTrees +open FSharpLint.Framework.MergeSyntaxTrees open NUnit.Framework open FParsec open TestUtils diff --git a/tests/FSharpLint.Core.Tests/Framework/TestHintParser.fs b/tests/FSharpLint.Core.Tests/Framework/TestHintParser.fs index 6ff858a83..d979325ce 100644 --- a/tests/FSharpLint.Core.Tests/Framework/TestHintParser.fs +++ b/tests/FSharpLint.Core.Tests/Framework/TestHintParser.fs @@ -6,7 +6,8 @@ open NUnit.Framework open FSharpLint.Framework open FSharpLint.Framework.HintParser open FParsec -open MergeSyntaxTrees +open FSharpLint.Framework.MergeSyntaxTrees +open FSharpLint.Framework.HintParserTypes open System.Collections.Generic [] diff --git a/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs b/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs index e8b293de2..2461d5bd6 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs @@ -6,7 +6,7 @@ open FSharp.Compiler.CodeAnalysis open FSharpLint.Application open FSharpLint.Framework open FSharpLint.Framework.HintParser -open FSharpLint.Framework.HintParser.MergeSyntaxTrees +open FSharpLint.Framework.MergeSyntaxTrees open FSharpLint.Framework.ParseFile open FSharpLint.Rules