Added a check for unused imports
This commit is contained in:
parent
2293603923
commit
87ae78c86a
@ -143,7 +143,7 @@ This is what generates expressions from tokens.
|
||||
|
||||
- [x] Unused variables
|
||||
- [x] Unused parameters
|
||||
- [ ] Unused imports
|
||||
- [x] Unused imports
|
||||
- [ ] Unnecessary newlines
|
||||
- [ ] Ineffective assignments
|
||||
|
||||
|
@ -20,10 +20,10 @@ func New(files ...string) *Build {
|
||||
}
|
||||
}
|
||||
|
||||
// Run parses the input files and generates an executable file.
|
||||
// Run compiles the input files.
|
||||
func (build *Build) Run() (compiler.Result, error) {
|
||||
functions, errors := scanner.Scan(build.Files)
|
||||
return compiler.Compile(functions, errors)
|
||||
files, functions, errors := scanner.Scan(build.Files)
|
||||
return compiler.Compile(files, functions, errors)
|
||||
}
|
||||
|
||||
// Executable returns the path to the executable.
|
||||
|
@ -5,15 +5,34 @@ import (
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/core"
|
||||
"git.akyoto.dev/cli/q/src/build/errors"
|
||||
"git.akyoto.dev/cli/q/src/build/fs"
|
||||
)
|
||||
|
||||
// Compile waits for the scan to finish and compiles all functions.
|
||||
func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error) {
|
||||
func Compile(files <-chan *fs.File, functions <-chan *core.Function, errs <-chan error) (Result, error) {
|
||||
result := Result{}
|
||||
all := map[string]*core.Function{}
|
||||
allFunctions := map[string]*core.Function{}
|
||||
allFiles := map[string]*fs.File{}
|
||||
|
||||
for functions != nil || errs != nil {
|
||||
for functions != nil || files != nil || errs != nil {
|
||||
select {
|
||||
case function, ok := <-functions:
|
||||
if !ok {
|
||||
functions = nil
|
||||
continue
|
||||
}
|
||||
|
||||
function.Functions = allFunctions
|
||||
allFunctions[function.Name] = function
|
||||
|
||||
case file, ok := <-files:
|
||||
if !ok {
|
||||
files = nil
|
||||
continue
|
||||
}
|
||||
|
||||
allFiles[file.Path] = file
|
||||
|
||||
case err, ok := <-errs:
|
||||
if !ok {
|
||||
errs = nil
|
||||
@ -21,23 +40,14 @@ func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error)
|
||||
}
|
||||
|
||||
return result, err
|
||||
|
||||
case function, ok := <-functions:
|
||||
if !ok {
|
||||
functions = nil
|
||||
continue
|
||||
}
|
||||
|
||||
function.Functions = all
|
||||
all[function.Name] = function
|
||||
}
|
||||
}
|
||||
|
||||
// Start parallel compilation
|
||||
CompileFunctions(all)
|
||||
CompileFunctions(allFunctions)
|
||||
|
||||
// Report errors if any occurred
|
||||
for _, function := range all {
|
||||
for _, function := range allFunctions {
|
||||
if function.Err != nil {
|
||||
return result, function.Err
|
||||
}
|
||||
@ -46,15 +56,24 @@ func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error)
|
||||
result.DataCount += len(function.Assembler.Data)
|
||||
}
|
||||
|
||||
// Check for unused imports in all files
|
||||
for _, file := range allFiles {
|
||||
for _, pkg := range file.Imports {
|
||||
if !pkg.Used {
|
||||
return result, errors.New(&errors.UnusedImport{Package: pkg.Path}, file, pkg.Position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for existence of `main`
|
||||
main, exists := all["main.main"]
|
||||
main, exists := allFunctions["main.main"]
|
||||
|
||||
if !exists {
|
||||
return result, errors.MissingMainFunction
|
||||
}
|
||||
|
||||
result.Main = main
|
||||
result.Functions = all
|
||||
result.Functions = allFunctions
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
@ -30,6 +30,20 @@ func (f *Function) CompileCall(root *expression.Expression) error {
|
||||
isSyscall := name == "syscall"
|
||||
|
||||
if !isSyscall {
|
||||
if pkg != f.File.Package {
|
||||
if f.File.Imports == nil {
|
||||
return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position)
|
||||
}
|
||||
|
||||
imp, exists := f.File.Imports[pkg]
|
||||
|
||||
if !exists {
|
||||
return errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameRoot.Token.Position)
|
||||
}
|
||||
|
||||
imp.Used = true
|
||||
}
|
||||
|
||||
tmp := strings.Builder{}
|
||||
tmp.WriteString(pkg)
|
||||
tmp.WriteString(".")
|
||||
@ -38,7 +52,7 @@ func (f *Function) CompileCall(root *expression.Expression) error {
|
||||
_, exists := f.Functions[fullName]
|
||||
|
||||
if !exists {
|
||||
return errors.New(&errors.UnknownFunction{Name: name}, f.File, root.Children[0].Token.Position)
|
||||
return errors.New(&errors.UnknownFunction{Name: name}, f.File, nameRoot.Token.Position)
|
||||
}
|
||||
}
|
||||
|
||||
|
18
src/build/errors/UnknownPackage.go
Normal file
18
src/build/errors/UnknownPackage.go
Normal file
@ -0,0 +1,18 @@
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// UnknownPackage represents unknown package errors.
|
||||
type UnknownPackage struct {
|
||||
Name string
|
||||
CorrectName string
|
||||
}
|
||||
|
||||
// Error generates the string representation.
|
||||
func (err *UnknownPackage) Error() string {
|
||||
if err.CorrectName != "" {
|
||||
return fmt.Sprintf("Unknown package '%s', did you mean '%s'?", err.Name, err.CorrectName)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Unknown package '%s'", err.Name)
|
||||
}
|
13
src/build/errors/UnusedImport.go
Normal file
13
src/build/errors/UnusedImport.go
Normal file
@ -0,0 +1,13 @@
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// UnusedImport error is created when an import is never used.
|
||||
type UnusedImport struct {
|
||||
Package string
|
||||
}
|
||||
|
||||
// Error generates the string representation.
|
||||
func (err *UnusedImport) Error() string {
|
||||
return fmt.Sprintf("Unused import '%s'", err.Package)
|
||||
}
|
@ -4,7 +4,9 @@ import "git.akyoto.dev/cli/q/src/build/token"
|
||||
|
||||
// File represents a single source file.
|
||||
type File struct {
|
||||
Path string
|
||||
Bytes []byte
|
||||
Tokens token.List
|
||||
Path string
|
||||
Package string
|
||||
Bytes []byte
|
||||
Imports map[string]*Import
|
||||
Tokens token.List
|
||||
}
|
||||
|
11
src/build/fs/Import.go
Normal file
11
src/build/fs/Import.go
Normal file
@ -0,0 +1,11 @@
|
||||
package fs
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/token"
|
||||
|
||||
// Import represents an import statement in a file.
|
||||
type Import struct {
|
||||
Path string
|
||||
FullPath string
|
||||
Position token.Position
|
||||
Used bool
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
package scanner
|
||||
|
||||
import "git.akyoto.dev/cli/q/src/build/core"
|
||||
import (
|
||||
"git.akyoto.dev/cli/q/src/build/core"
|
||||
"git.akyoto.dev/cli/q/src/build/fs"
|
||||
)
|
||||
|
||||
// Scan scans the list of files.
|
||||
func Scan(files []string) (<-chan *core.Function, <-chan error) {
|
||||
func Scan(files []string) (<-chan *fs.File, <-chan *core.Function, <-chan error) {
|
||||
scanner := Scanner{
|
||||
files: make(chan *fs.File),
|
||||
functions: make(chan *core.Function),
|
||||
errors: make(chan error),
|
||||
}
|
||||
@ -12,9 +16,10 @@ func Scan(files []string) (<-chan *core.Function, <-chan error) {
|
||||
go func() {
|
||||
scanner.queue(files...)
|
||||
scanner.group.Wait()
|
||||
close(scanner.files)
|
||||
close(scanner.functions)
|
||||
close(scanner.errors)
|
||||
}()
|
||||
|
||||
return scanner.functions, scanner.errors
|
||||
return scanner.files, scanner.functions, scanner.errors
|
||||
}
|
||||
|
@ -4,10 +4,12 @@ import (
|
||||
"sync"
|
||||
|
||||
"git.akyoto.dev/cli/q/src/build/core"
|
||||
"git.akyoto.dev/cli/q/src/build/fs"
|
||||
)
|
||||
|
||||
// Scanner is used to scan files before the actual compilation step.
|
||||
type Scanner struct {
|
||||
files chan *fs.File
|
||||
functions chan *core.Function
|
||||
errors chan error
|
||||
queued sync.Map
|
||||
|
@ -26,11 +26,14 @@ func (s *Scanner) scanFile(path string, pkg string) error {
|
||||
tokens := token.Tokenize(contents)
|
||||
|
||||
file := &fs.File{
|
||||
Path: path,
|
||||
Bytes: contents,
|
||||
Tokens: tokens,
|
||||
Path: path,
|
||||
Bytes: contents,
|
||||
Tokens: tokens,
|
||||
Package: pkg,
|
||||
}
|
||||
|
||||
s.files <- file
|
||||
|
||||
var (
|
||||
i = 0
|
||||
groupLevel = 0
|
||||
@ -50,8 +53,20 @@ func (s *Scanner) scanFile(path string, pkg string) error {
|
||||
}
|
||||
|
||||
packageName := tokens[i].Text(contents)
|
||||
s.queueDirectory(filepath.Join(config.Library, packageName), packageName)
|
||||
|
||||
if file.Imports == nil {
|
||||
file.Imports = map[string]*fs.Import{}
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(config.Library, packageName)
|
||||
|
||||
file.Imports[packageName] = &fs.Import{
|
||||
Path: packageName,
|
||||
FullPath: fullPath,
|
||||
Position: tokens[i].Position,
|
||||
}
|
||||
|
||||
s.queueDirectory(fullPath, packageName)
|
||||
i++
|
||||
|
||||
if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF {
|
||||
|
3
tests/errors/UnknownPackage.q
Normal file
3
tests/errors/UnknownPackage.q
Normal file
@ -0,0 +1,3 @@
|
||||
main() {
|
||||
sys.read()
|
||||
}
|
3
tests/errors/UnusedImport.q
Normal file
3
tests/errors/UnusedImport.q
Normal file
@ -0,0 +1,3 @@
|
||||
import sys
|
||||
|
||||
main(){}
|
@ -40,6 +40,8 @@ var errs = []struct {
|
||||
{"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}},
|
||||
{"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}},
|
||||
{"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}},
|
||||
{"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}},
|
||||
{"UnusedImport.q", &errors.UnusedImport{Package: "sys"}},
|
||||
{"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}},
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user