Last active
May 22, 2024 23:38
-
-
Save brianrourkeboll/8606003397d30157d7c520ffca174190 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#r "nuget: FSharp.Compiler.Service" | |
open FSharp.Compiler.Syntax | |
open FSharp.Compiler.SyntaxTrivia | |
open FSharp.Compiler.Xml | |
type Node = | |
{ Data : Data | |
Children : Node list } | |
and Data = | |
{ Key : Key | |
Count : int } | |
and Key = | |
| Namespace of name : string | |
| Module of name : string | |
| Type of name : string | |
| Binding of name : string | |
module Node = | |
let create (key, count) children = | |
{ Data = | |
{ Key = key | |
Count = count } | |
Children = children } | |
let merge count node = { node with Node.Data.Count = node.Data.Count + count } | |
let rec loopLevels acc = function | |
| [] -> List.rev acc | |
| level :: levels -> loopLevels (loopNodes [] (acc, level)) levels | |
and loopNodes acc = function | |
| [], [] -> | |
acc | |
| [], (key, count) :: level -> | |
loopLevel (fun children -> Node.create (key, count) children) level :: acc | |
| node :: nodes, (key, count) :: level when node.Data.Key = key -> | |
loopRest ({ Node.merge count node with Children = loopNodes [] (node.Children, level) } :: acc) nodes | |
| node :: nodes, level -> | |
loopNodes (node :: acc) (nodes, level) | |
and loopLevel cont = function | |
| [] -> cont [] | |
| (key, count) :: level -> | |
loopLevel (fun children -> cont [Node.create (key, count) children]) level | |
and loopRest acc = function | |
| [] -> acc | |
| node :: nodes -> loopRest (node :: acc) nodes | |
let cyclomaticComplexity ast = | |
let counts = | |
(Map.empty, ast) | |
||> ParsedInput.fold (fun counts path node -> | |
let key path = | |
([], path) | |
||> List.fold (fun acc node -> | |
let (|Ident|) (ident : Ident) = ident.idText | |
let (|LongIdent|) (longIdent : LongIdent) = longIdent |> List.map (|Ident|) |> String.concat "." | |
match node with | |
| SyntaxNode.SynModuleOrNamespace (SynModuleOrNamespace (longId = LongIdent name; kind = SynModuleOrNamespaceKind.DeclaredNamespace)) -> | |
Namespace name :: acc | |
| SyntaxNode.SynModuleOrNamespace (SynModuleOrNamespace (longId = LongIdent name)) | |
| SyntaxNode.SynModule (SynModuleDecl.NestedModule (moduleInfo = SynComponentInfo (longId = LongIdent name))) -> | |
Module name :: acc | |
| SyntaxNode.SynTypeDefn (SynTypeDefn (typeInfo = SynComponentInfo (longId = LongIdent name))) -> | |
Type name :: acc | |
| SyntaxNode.SynBinding (SynBinding (headPat = SynPat.Named (ident = SynIdent (ident = Ident name)))) | |
| SyntaxNode.SynBinding (SynBinding (headPat = SynPat.LongIdent (longDotId = SynLongIdent (id = LongIdent name)))) -> | |
Binding name :: acc | |
| _ -> acc) | |
let (|Clauses|) = function [] | [_] -> 0 | clauses -> clauses.Length | |
match node with | |
| SyntaxNode.SynModuleOrNamespace _ | |
| SyntaxNode.SynModule _ | |
| SyntaxNode.SynTypeDefn _ -> | |
counts |> Map.add (key (node :: path)) 0 | |
| SyntaxNode.SynBinding _ -> | |
counts |> Map.add (key (node :: path)) 1 | |
| SyntaxNode.SynExpr (SynExpr.IfThenElse _) | |
| SyntaxNode.SynExpr (SynExpr.For _) | |
| SyntaxNode.SynExpr (SynExpr.ForEach _) | |
| SyntaxNode.SynExpr (SynExpr.While _) | |
| SyntaxNode.SynExpr (SynExpr.WhileBang _) | |
| SyntaxNode.SynExpr (SynExpr.TryWith _) | |
| SyntaxNode.SynExpr (SynExpr.TryFinally _) -> | |
counts |> Map.change (key path) (Option.map ((+) 1)) | |
| SyntaxNode.SynExpr (SynExpr.Match (clauses = Clauses count)) | |
| SyntaxNode.SynExpr (SynExpr.MatchBang (clauses = Clauses count)) | |
| SyntaxNode.SynExpr (SynExpr.MatchLambda (matchClauses = Clauses count)) | |
| SyntaxNode.SynExpr (SynExpr.TryWith (withCases = Clauses count)) -> | |
counts |> Map.change (key path) (Option.map ((+) count)) | |
| _ -> counts) | |
let levels = | |
counts | |
|> Map.toList | |
|> List.map (fun (level, count) -> level |> List.map (fun key -> key, count)) | |
loopLevels [] levels | |
let R _ = FSharp.Compiler.Text.Range.range0 | |
/// https://fsprojects.github.io/fantomas-tools/#/ast?data=N4KABGBEAmCmBmBLAdrAzpAXFSAacUiaAYmolmPAIYA2as%2BEkAxgPZwWQC27ArjbDABZMAF4AOgCdxyCBAEAXSmAAeaMVJlztYRPDAAZIgoB0RAKJcADgoCeq9QoAWsWQEZNsnRFh1Yn728uKgVmJwcwAHdEZwDA7QAfMABtAH0AXTAAWgA%2BMAAmOPiIJNTsvIBmTyKwRTAAczAACgBKDWkvb3hWSVVddxMTNwAGMGhWGvjW6o7tHmh%2BQQA5dq1AuvCVMVUZmXnFsAB5Vc7a2CVu1jAAIype0Ru7vAJIWBUrKmRoCgVJXlgQABfIA | |
/// | |
/// module M = | |
/// let f xs = | |
/// if List.isEmpty xs then 1 | |
/// else | |
/// match xs with | |
/// | [_] -> 2 | |
/// | _ -> 3 | |
/// | |
/// let g () = | |
/// for x in 1..10 do | |
/// () | |
/// | |
/// module N = | |
/// let h x = x | |
/// | |
/// module O = | |
/// let foo bar = bar | |
let ast = | |
ParsedInput.ImplFile( | |
ParsedImplFileInput.ParsedImplFileInput( | |
fileName = "tmp.fsx", | |
isScript = true, | |
qualifiedNameOfFile = QualifiedNameOfFile.QualifiedNameOfFile(Ident("Tmp$fsx", R("(1,0--17,21)"))), | |
scopedPragmas = [], | |
hashDirectives = [], | |
contents = [ | |
SynModuleOrNamespace.SynModuleOrNamespace( | |
longId = [ Ident("Tmp", R("(1,0--1,0)")) ], | |
isRecursive = false, | |
kind = SynModuleOrNamespaceKind.AnonModule, | |
decls = [ | |
SynModuleDecl.NestedModule( | |
moduleInfo = | |
SynComponentInfo.SynComponentInfo( | |
attributes = [], | |
typeParams = None, | |
constraints = [], | |
longId = [ Ident("M", R("(1,7--1,8)")) ], | |
xmlDoc = PreXmlDoc.Empty, | |
preferPostfix = false, | |
accessibility = None, | |
range = R("(1,0--1,8)") | |
), | |
isRecursive = false, | |
decls = [ | |
SynModuleDecl.Let( | |
isRecursive = false, | |
bindings = [ | |
SynBinding.SynBinding( | |
accessibility = None, | |
kind = SynBindingKind.Normal, | |
isInline = false, | |
isMutable = false, | |
attributes = [], | |
xmlDoc = PreXmlDoc.Empty, | |
valData = | |
SynValData.SynValData( | |
memberFlags = None, | |
valInfo = | |
SynValInfo.SynValInfo( | |
curriedArgInfos = [ | |
[ | |
SynArgInfo.SynArgInfo(attributes = [], optional = false, ident = Some(Ident("xs", R("(2,10--2,12)")))) | |
] | |
], | |
returnInfo = SynArgInfo.SynArgInfo(attributes = [], optional = false, ident = None) | |
), | |
thisIdOpt = None | |
), | |
headPat = | |
SynPat.LongIdent( | |
longDotId = SynLongIdent.SynLongIdent(id = [ Ident("f", R("(2,8--2,9)")) ], dotRanges = [], trivia = [ None ]), | |
extraId = None, | |
typarDecls = None, | |
argPats = | |
SynArgPats.Pats( | |
[ | |
SynPat.Named( | |
ident = SynIdent.SynIdent(ident = Ident("xs", R("(2,10--2,12)")), trivia = None), | |
isThisVal = false, | |
accessibility = None, | |
range = R("(2,10--2,12)") | |
) | |
] | |
), | |
accessibility = None, | |
range = R("(2,8--2,12)") | |
), | |
returnInfo = None, | |
expr = | |
SynExpr.IfThenElse( | |
ifExpr = | |
SynExpr.App( | |
flag = ExprAtomicFlag.NonAtomic, | |
isInfix = false, | |
funcExpr = | |
SynExpr.LongIdent( | |
isOptional = false, | |
longDotId = | |
SynLongIdent.SynLongIdent( | |
id = [ Ident("List", R("(3,11--3,15)")); Ident("isEmpty", R("(3,16--3,23)")) ], | |
dotRanges = [ R("(3,15--3,16)") ], | |
trivia = [ None; None ] | |
), | |
altNameRefCell = None, | |
range = R("(3,11--3,23)") | |
), | |
argExpr = SynExpr.Ident(Ident("xs", R("(3,24--3,26)"))), | |
range = R("(3,11--3,26)") | |
), | |
thenExpr = SynExpr.Const(constant = SynConst.Int32(1), range = R("(3,32--3,33)")), | |
elseExpr = | |
Some( | |
SynExpr.Match( | |
matchDebugPoint = DebugPointAtBinding.Yes(R("(5,12--5,25)")), | |
expr = SynExpr.Ident(Ident("xs", R("(5,18--5,20)"))), | |
clauses = [ | |
SynMatchClause.SynMatchClause( | |
pat = SynPat.ArrayOrList(isArray = false, elementPats = [ SynPat.Wild(R("(6,15--6,16)")) ], range = R("(6,14--6,17)")), | |
whenExpr = None, | |
resultExpr = SynExpr.Const(constant = SynConst.Int32(2), range = R("(6,21--6,22)")), | |
range = R("(6,14--6,22)"), | |
debugPoint = DebugPointAtTarget.Yes, | |
trivia = { | |
ArrowRange = Some(R("(6,18--6,20)")) | |
BarRange = Some(R("(6,12--6,13)")) | |
} | |
) | |
SynMatchClause.SynMatchClause( | |
pat = SynPat.Wild(R("(7,14--7,15)")), | |
whenExpr = None, | |
resultExpr = SynExpr.Const(constant = SynConst.Int32(3), range = R("(7,19--7,20)")), | |
range = R("(7,14--7,20)"), | |
debugPoint = DebugPointAtTarget.Yes, | |
trivia = { | |
ArrowRange = Some(R("(7,16--7,18)")) | |
BarRange = Some(R("(7,12--7,13)")) | |
} | |
) | |
], | |
range = R("(5,12--7,20)"), | |
trivia = { | |
MatchKeyword = R("(5,12--5,17)") | |
WithKeyword = R("(5,21--5,25)") | |
} | |
) | |
), | |
spIfToThen = DebugPointAtBinding.Yes(R("(3,8--3,31)")), | |
isFromErrorRecovery = false, | |
range = R("(3,8--7,20)"), | |
trivia = { | |
IfKeyword = R("(3,8--3,10)") | |
IsElif = false | |
ThenKeyword = R("(3,27--3,31)") | |
ElseKeyword = Some(R("(4,8--4,12)")) | |
IfToThenRange = R("(3,8--3,31)") | |
} | |
), | |
range = R("(2,8--2,12)"), | |
debugPoint = DebugPointAtBinding.NoneAtLet, | |
trivia = { | |
LeadingKeyword = SynLeadingKeyword.Let(R("(2,4--2,7)")) | |
InlineKeyword = None | |
EqualsRange = Some(R("(2,13--2,14)")) | |
} | |
) | |
], | |
range = R("(2,4--7,20)") | |
) | |
SynModuleDecl.Let( | |
isRecursive = false, | |
bindings = [ | |
SynBinding.SynBinding( | |
accessibility = None, | |
kind = SynBindingKind.Normal, | |
isInline = false, | |
isMutable = false, | |
attributes = [], | |
xmlDoc = PreXmlDoc.Empty, | |
valData = | |
SynValData.SynValData( | |
memberFlags = None, | |
valInfo = SynValInfo.SynValInfo(curriedArgInfos = [ [] ], returnInfo = SynArgInfo.SynArgInfo(attributes = [], optional = false, ident = None)), | |
thisIdOpt = None | |
), | |
headPat = | |
SynPat.LongIdent( | |
longDotId = SynLongIdent.SynLongIdent(id = [ Ident("g", R("(9,8--9,9)")) ], dotRanges = [], trivia = [ None ]), | |
extraId = None, | |
typarDecls = None, | |
argPats = | |
SynArgPats.Pats( | |
[ | |
SynPat.Paren(pat = SynPat.Const(constant = SynConst.Unit, range = R("(9,10--9,12)")), range = R("(9,10--9,12)")) | |
] | |
), | |
accessibility = None, | |
range = R("(9,8--9,12)") | |
), | |
returnInfo = None, | |
expr = | |
SynExpr.ForEach( | |
forDebugPoint = DebugPointAtFor.Yes(R("(10,8--10,11)")), | |
inDebugPoint = DebugPointAtInOrTo.Yes(R("(10,14--10,16)")), | |
seqExprOnly = SeqExprOnly.SeqExprOnly(false), | |
isFromSource = true, | |
pat = | |
SynPat.Named( | |
ident = SynIdent.SynIdent(ident = Ident("x", R("(10,12--10,13)")), trivia = None), | |
isThisVal = false, | |
accessibility = None, | |
range = R("(10,12--10,13)") | |
), | |
enumExpr = | |
SynExpr.IndexRange( | |
expr1 = Some(SynExpr.Const(constant = SynConst.Int32(1), range = R("(10,17--10,18)"))), | |
opm = R("(10,18--10,20)"), | |
expr2 = Some(SynExpr.Const(constant = SynConst.Int32(10), range = R("(10,20--10,22)"))), | |
range1 = R("(10,17--10,18)"), | |
range2 = R("(10,20--10,22)"), | |
range = R("(10,17--10,22)") | |
), | |
bodyExpr = SynExpr.Const(constant = SynConst.Unit, range = R("(11,12--11,14)")), | |
range = R("(10,8--11,14)") | |
), | |
range = R("(9,8--9,12)"), | |
debugPoint = DebugPointAtBinding.NoneAtLet, | |
trivia = { | |
LeadingKeyword = SynLeadingKeyword.Let(R("(9,4--9,7)")) | |
InlineKeyword = None | |
EqualsRange = Some(R("(9,13--9,14)")) | |
} | |
) | |
], | |
range = R("(9,4--11,14)") | |
) | |
SynModuleDecl.NestedModule( | |
moduleInfo = | |
SynComponentInfo.SynComponentInfo( | |
attributes = [], | |
typeParams = None, | |
constraints = [], | |
longId = [ Ident("N", R("(13,11--13,12)")) ], | |
xmlDoc = PreXmlDoc.Empty, | |
preferPostfix = false, | |
accessibility = None, | |
range = R("(13,4--13,12)") | |
), | |
isRecursive = false, | |
decls = [ | |
SynModuleDecl.Let( | |
isRecursive = false, | |
bindings = [ | |
SynBinding.SynBinding( | |
accessibility = None, | |
kind = SynBindingKind.Normal, | |
isInline = false, | |
isMutable = false, | |
attributes = [], | |
xmlDoc = PreXmlDoc.Empty, | |
valData = | |
SynValData.SynValData( | |
memberFlags = None, | |
valInfo = | |
SynValInfo.SynValInfo( | |
curriedArgInfos = [ | |
[ | |
SynArgInfo.SynArgInfo(attributes = [], optional = false, ident = Some(Ident("x", R("(14,14--14,15)")))) | |
] | |
], | |
returnInfo = SynArgInfo.SynArgInfo(attributes = [], optional = false, ident = None) | |
), | |
thisIdOpt = None | |
), | |
headPat = | |
SynPat.LongIdent( | |
longDotId = SynLongIdent.SynLongIdent(id = [ Ident("h", R("(14,12--14,13)")) ], dotRanges = [], trivia = [ None ]), | |
extraId = None, | |
typarDecls = None, | |
argPats = | |
SynArgPats.Pats( | |
[ | |
SynPat.Named( | |
ident = SynIdent.SynIdent(ident = Ident("x", R("(14,14--14,15)")), trivia = None), | |
isThisVal = false, | |
accessibility = None, | |
range = R("(14,14--14,15)") | |
) | |
] | |
), | |
accessibility = None, | |
range = R("(14,12--14,15)") | |
), | |
returnInfo = None, | |
expr = SynExpr.Ident(Ident("x", R("(14,18--14,19)"))), | |
range = R("(14,12--14,15)"), | |
debugPoint = DebugPointAtBinding.NoneAtLet, | |
trivia = { | |
LeadingKeyword = SynLeadingKeyword.Let(R("(14,8--14,11)")) | |
InlineKeyword = None | |
EqualsRange = Some(R("(14,16--14,17)")) | |
} | |
) | |
], | |
range = R("(14,8--14,19)") | |
) | |
], | |
isContinuing = false, | |
range = R("(13,4--14,19)"), | |
trivia = { | |
ModuleKeyword = Some(R("(13,4--13,10)")) | |
EqualsRange = Some(R("(13,13--13,14)")) | |
} | |
) | |
], | |
isContinuing = false, | |
range = R("(1,0--14,19)"), | |
trivia = { | |
ModuleKeyword = Some(R("(1,0--1,6)")) | |
EqualsRange = Some(R("(1,9--1,10)")) | |
} | |
) | |
SynModuleDecl.NestedModule( | |
moduleInfo = | |
SynComponentInfo.SynComponentInfo( | |
attributes = [], | |
typeParams = None, | |
constraints = [], | |
longId = [ Ident("O", R("(16,7--16,8)")) ], | |
xmlDoc = PreXmlDoc.Empty, | |
preferPostfix = false, | |
accessibility = None, | |
range = R("(16,0--16,8)") | |
), | |
isRecursive = false, | |
decls = [ | |
SynModuleDecl.Let( | |
isRecursive = false, | |
bindings = [ | |
SynBinding.SynBinding( | |
accessibility = None, | |
kind = SynBindingKind.Normal, | |
isInline = false, | |
isMutable = false, | |
attributes = [], | |
xmlDoc = PreXmlDoc.Empty, | |
valData = | |
SynValData.SynValData( | |
memberFlags = None, | |
valInfo = | |
SynValInfo.SynValInfo( | |
curriedArgInfos = [ | |
[ | |
SynArgInfo.SynArgInfo(attributes = [], optional = false, ident = Some(Ident("bar", R("(17,12--17,15)")))) | |
] | |
], | |
returnInfo = SynArgInfo.SynArgInfo(attributes = [], optional = false, ident = None) | |
), | |
thisIdOpt = None | |
), | |
headPat = | |
SynPat.LongIdent( | |
longDotId = SynLongIdent.SynLongIdent(id = [ Ident("foo", R("(17,8--17,11)")) ], dotRanges = [], trivia = [ None ]), | |
extraId = None, | |
typarDecls = None, | |
argPats = | |
SynArgPats.Pats( | |
[ | |
SynPat.Named( | |
ident = SynIdent.SynIdent(ident = Ident("bar", R("(17,12--17,15)")), trivia = None), | |
isThisVal = false, | |
accessibility = None, | |
range = R("(17,12--17,15)") | |
) | |
] | |
), | |
accessibility = None, | |
range = R("(17,8--17,15)") | |
), | |
returnInfo = None, | |
expr = SynExpr.Ident(Ident("bar", R("(17,18--17,21)"))), | |
range = R("(17,8--17,15)"), | |
debugPoint = DebugPointAtBinding.NoneAtLet, | |
trivia = { | |
LeadingKeyword = SynLeadingKeyword.Let(R("(17,4--17,7)")) | |
InlineKeyword = None | |
EqualsRange = Some(R("(17,16--17,17)")) | |
} | |
) | |
], | |
range = R("(17,4--17,21)") | |
) | |
], | |
isContinuing = false, | |
range = R("(16,0--17,21)"), | |
trivia = { | |
ModuleKeyword = Some(R("(16,0--16,6)")) | |
EqualsRange = Some(R("(16,9--16,10)")) | |
} | |
) | |
], | |
xmlDoc = PreXmlDoc.Empty, | |
attribs = [], | |
accessibility = None, | |
range = R("(1,0--17,21)"), | |
trivia = { | |
LeadingKeyword = SynModuleOrNamespaceLeadingKeyword.None | |
} | |
) | |
], | |
flags = (false, false), | |
trivia = { | |
ConditionalDirectives = [] | |
CodeComments = [] | |
}, | |
identifiers = set [] | |
) | |
) | |
let cyclomaticComplexities = cyclomaticComplexity ast | |
//val cyclomaticComplexities: Node list = | |
// [{ Data = { Key = Module "Tmp" | |
// Count = 8 } | |
// Children = | |
// [{ Data = { Key = Module "M" | |
// Count = 7 } | |
// Children = | |
// [{ Data = { Key = Binding "g" | |
// Count = 2 } | |
// Children = [] }; { Data = { Key = Module "N" | |
// Count = 1 } | |
// Children = [{ Data = { Key = Binding "h" | |
// Count = 1 } | |
// Children = [] }] }; | |
// { Data = { Key = Binding "f" | |
// Count = 4 } | |
// Children = [] }] }; { Data = { Key = Module "O" | |
// Count = 1 } | |
// Children = [{ Data = { Key = Binding "foo" | |
// Count = 1 } | |
// Children = [] }] }] }] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment