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 variables
|
||||||
- [x] Unused parameters
|
- [x] Unused parameters
|
||||||
- [ ] Unused imports
|
- [x] Unused imports
|
||||||
- [ ] Unnecessary newlines
|
- [ ] Unnecessary newlines
|
||||||
- [ ] Ineffective assignments
|
- [ ] 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) {
|
func (build *Build) Run() (compiler.Result, error) {
|
||||||
functions, errors := scanner.Scan(build.Files)
|
files, functions, errors := scanner.Scan(build.Files)
|
||||||
return compiler.Compile(functions, errors)
|
return compiler.Compile(files, functions, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Executable returns the path to the executable.
|
// 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/core"
|
||||||
"git.akyoto.dev/cli/q/src/build/errors"
|
"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.
|
// 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{}
|
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 {
|
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:
|
case err, ok := <-errs:
|
||||||
if !ok {
|
if !ok {
|
||||||
errs = nil
|
errs = nil
|
||||||
@ -21,23 +40,14 @@ func Compile(functions <-chan *core.Function, errs <-chan error) (Result, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result, err
|
return result, err
|
||||||
|
|
||||||
case function, ok := <-functions:
|
|
||||||
if !ok {
|
|
||||||
functions = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
function.Functions = all
|
|
||||||
all[function.Name] = function
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start parallel compilation
|
// Start parallel compilation
|
||||||
CompileFunctions(all)
|
CompileFunctions(allFunctions)
|
||||||
|
|
||||||
// Report errors if any occurred
|
// Report errors if any occurred
|
||||||
for _, function := range all {
|
for _, function := range allFunctions {
|
||||||
if function.Err != nil {
|
if function.Err != nil {
|
||||||
return result, function.Err
|
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)
|
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`
|
// Check for existence of `main`
|
||||||
main, exists := all["main.main"]
|
main, exists := allFunctions["main.main"]
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
return result, errors.MissingMainFunction
|
return result, errors.MissingMainFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Main = main
|
result.Main = main
|
||||||
result.Functions = all
|
result.Functions = allFunctions
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,20 @@ func (f *Function) CompileCall(root *expression.Expression) error {
|
|||||||
isSyscall := name == "syscall"
|
isSyscall := name == "syscall"
|
||||||
|
|
||||||
if !isSyscall {
|
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 := strings.Builder{}
|
||||||
tmp.WriteString(pkg)
|
tmp.WriteString(pkg)
|
||||||
tmp.WriteString(".")
|
tmp.WriteString(".")
|
||||||
@ -38,7 +52,7 @@ func (f *Function) CompileCall(root *expression.Expression) error {
|
|||||||
_, exists := f.Functions[fullName]
|
_, exists := f.Functions[fullName]
|
||||||
|
|
||||||
if !exists {
|
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.
|
// File represents a single source file.
|
||||||
type File struct {
|
type File struct {
|
||||||
Path string
|
Path string
|
||||||
Bytes []byte
|
Package string
|
||||||
Tokens token.List
|
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
|
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.
|
// 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{
|
scanner := Scanner{
|
||||||
|
files: make(chan *fs.File),
|
||||||
functions: make(chan *core.Function),
|
functions: make(chan *core.Function),
|
||||||
errors: make(chan error),
|
errors: make(chan error),
|
||||||
}
|
}
|
||||||
@ -12,9 +16,10 @@ func Scan(files []string) (<-chan *core.Function, <-chan error) {
|
|||||||
go func() {
|
go func() {
|
||||||
scanner.queue(files...)
|
scanner.queue(files...)
|
||||||
scanner.group.Wait()
|
scanner.group.Wait()
|
||||||
|
close(scanner.files)
|
||||||
close(scanner.functions)
|
close(scanner.functions)
|
||||||
close(scanner.errors)
|
close(scanner.errors)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return scanner.functions, scanner.errors
|
return scanner.files, scanner.functions, scanner.errors
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,12 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"git.akyoto.dev/cli/q/src/build/core"
|
"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.
|
// Scanner is used to scan files before the actual compilation step.
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
|
files chan *fs.File
|
||||||
functions chan *core.Function
|
functions chan *core.Function
|
||||||
errors chan error
|
errors chan error
|
||||||
queued sync.Map
|
queued sync.Map
|
||||||
|
@ -26,11 +26,14 @@ func (s *Scanner) scanFile(path string, pkg string) error {
|
|||||||
tokens := token.Tokenize(contents)
|
tokens := token.Tokenize(contents)
|
||||||
|
|
||||||
file := &fs.File{
|
file := &fs.File{
|
||||||
Path: path,
|
Path: path,
|
||||||
Bytes: contents,
|
Bytes: contents,
|
||||||
Tokens: tokens,
|
Tokens: tokens,
|
||||||
|
Package: pkg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.files <- file
|
||||||
|
|
||||||
var (
|
var (
|
||||||
i = 0
|
i = 0
|
||||||
groupLevel = 0
|
groupLevel = 0
|
||||||
@ -50,8 +53,20 @@ func (s *Scanner) scanFile(path string, pkg string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
packageName := tokens[i].Text(contents)
|
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++
|
i++
|
||||||
|
|
||||||
if tokens[i].Kind != token.NewLine && tokens[i].Kind != token.EOF {
|
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"}},
|
{"UnknownIdentifier.q", &errors.UnknownIdentifier{Name: "x"}},
|
||||||
{"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}},
|
{"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}},
|
||||||
{"UnknownIdentifier3.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"}},
|
{"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user