aboutsummaryrefslogtreecommitdiff
path: root/inspect.go
diff options
context:
space:
mode:
Diffstat (limited to 'inspect.go')
-rw-r--r--inspect.go164
1 files changed, 164 insertions, 0 deletions
diff --git a/inspect.go b/inspect.go
new file mode 100644
index 0000000..a2e39c9
--- /dev/null
+++ b/inspect.go
@@ -0,0 +1,164 @@
+package main
+
+import (
+ "go/ast"
+ "go/token"
+ "reflect"
+)
+
+func isGeneralDeclarationScoped(generalDeclaration *ast.GenDecl) bool {
+ for _, specification := range generalDeclaration.Specs {
+ if typeSpecification, isTypeSpecification := specification.(*ast.TypeSpec); isTypeSpecification {
+ switch typeSpecification.Type.(type) {
+ case *ast.StructType, *ast.InterfaceType:
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+func (f *Formatter) buildLineInfo(tokenFileSet *token.FileSet, parsedFile *ast.File) map[int]*lineInformation {
+ lineInformationMap := make(map[int]*lineInformation)
+ tokenFile := tokenFileSet.File(parsedFile.Pos())
+
+ if tokenFile == nil {
+ return lineInformationMap
+ }
+
+ for _, declaration := range parsedFile.Decls {
+ startLine := tokenFile.Line(declaration.Pos())
+ endLine := tokenFile.Line(declaration.End())
+ statementType := ""
+ isScoped := false
+
+ switch typedDeclaration := declaration.(type) {
+ case *ast.GenDecl:
+ statementType = typedDeclaration.Tok.String()
+ isScoped = isGeneralDeclarationScoped(typedDeclaration)
+ case *ast.FuncDecl:
+ statementType = "func"
+ isScoped = true
+ default:
+ statementType = reflect.TypeOf(declaration).String()
+ }
+
+ lineInformationMap[startLine] = &lineInformation{statementType: statementType, isTopLevel: true, isScoped: isScoped, isStartLine: true}
+ lineInformationMap[endLine] = &lineInformation{statementType: statementType, isTopLevel: true, isScoped: isScoped, isStartLine: false}
+ }
+
+ ast.Inspect(parsedFile, func(astNode ast.Node) bool {
+ if astNode == nil {
+ return true
+ }
+
+ switch typedNode := astNode.(type) {
+ case *ast.BlockStmt:
+ f.processBlock(tokenFile, typedNode, lineInformationMap)
+ case *ast.CaseClause:
+ f.processStatementList(tokenFile, typedNode.Body, lineInformationMap)
+ case *ast.CommClause:
+ f.processStatementList(tokenFile, typedNode.Body, lineInformationMap)
+ }
+
+ return true
+ })
+
+ return lineInformationMap
+}
+
+func (f *Formatter) processBlock(tokenFile *token.File, blockStatement *ast.BlockStmt, lineInformationMap map[int]*lineInformation) {
+ if blockStatement == nil {
+ return
+ }
+
+ f.processStatementList(tokenFile, blockStatement.List, lineInformationMap)
+}
+
+func (f *Formatter) processStatementList(tokenFile *token.File, statements []ast.Stmt, lineInformationMap map[int]*lineInformation) {
+ for _, statement := range statements {
+ startLine := tokenFile.Line(statement.Pos())
+ endLine := tokenFile.Line(statement.End())
+ statementType := ""
+ isScoped := false
+
+ switch typedStatement := statement.(type) {
+ case *ast.DeclStmt:
+ if generalDeclaration, isGeneralDeclaration := typedStatement.Decl.(*ast.GenDecl); isGeneralDeclaration {
+ statementType = generalDeclaration.Tok.String()
+ } else {
+ statementType = reflect.TypeOf(statement).String()
+ }
+ case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt,
+ *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.BlockStmt:
+ statementType = reflect.TypeOf(statement).String()
+ isScoped = true
+ default:
+ statementType = reflect.TypeOf(statement).String()
+ }
+
+ existingStart := lineInformationMap[startLine]
+
+ if existingStart == nil || !existingStart.isStartLine {
+ lineInformationMap[startLine] = &lineInformation{statementType: statementType, isTopLevel: false, isScoped: isScoped, isStartLine: true}
+ }
+
+ existingEnd := lineInformationMap[endLine]
+
+ if existingEnd == nil || !existingEnd.isStartLine {
+ lineInformationMap[endLine] = &lineInformation{statementType: statementType, isTopLevel: false, isScoped: isScoped, isStartLine: false}
+ }
+
+ switch typedStatement := statement.(type) {
+ case *ast.IfStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+
+ if typedStatement.Else != nil {
+ if elseBlock, isBlockStatement := typedStatement.Else.(*ast.BlockStmt); isBlockStatement {
+ f.processBlock(tokenFile, elseBlock, lineInformationMap)
+ } else if elseIfStatement, isIfStatement := typedStatement.Else.(*ast.IfStmt); isIfStatement {
+ f.processIfStatement(tokenFile, elseIfStatement, lineInformationMap)
+ }
+ }
+ case *ast.ForStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.RangeStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.SwitchStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.TypeSwitchStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.SelectStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.BlockStmt:
+ f.processBlock(tokenFile, typedStatement, lineInformationMap)
+ }
+ }
+}
+
+func (f *Formatter) processIfStatement(tokenFile *token.File, ifStatement *ast.IfStmt, lineInformationMap map[int]*lineInformation) {
+ startLine := tokenFile.Line(ifStatement.Pos())
+ endLine := tokenFile.Line(ifStatement.End())
+ existingStart := lineInformationMap[startLine]
+
+ if existingStart == nil || !existingStart.isStartLine {
+ lineInformationMap[startLine] = &lineInformation{statementType: "*ast.IfStmt", isTopLevel: false, isScoped: true, isStartLine: true}
+ }
+
+ existingEnd := lineInformationMap[endLine]
+
+ if existingEnd == nil || !existingEnd.isStartLine {
+ lineInformationMap[endLine] = &lineInformation{statementType: "*ast.IfStmt", isTopLevel: false, isScoped: true, isStartLine: false}
+ }
+
+ f.processBlock(tokenFile, ifStatement.Body, lineInformationMap)
+
+ if ifStatement.Else != nil {
+ if elseBlock, isBlockStatement := ifStatement.Else.(*ast.BlockStmt); isBlockStatement {
+ f.processBlock(tokenFile, elseBlock, lineInformationMap)
+ } else if elseIfStatement, isIfStatement := ifStatement.Else.(*ast.IfStmt); isIfStatement {
+ f.processIfStatement(tokenFile, elseIfStatement, lineInformationMap)
+ }
+ }
+}