Skip to content

Commit d53b297

Browse files
committed
resolver: move NodeResolver to its own package
1 parent e48e641 commit d53b297

File tree

15 files changed

+163
-161
lines changed

15 files changed

+163
-161
lines changed

api.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/stephen/cssc/internal/printer"
1313
"github.com/stephen/cssc/internal/sources"
1414
"github.com/stephen/cssc/internal/transformer"
15+
"github.com/stephen/cssc/resolver"
1516
"github.com/stephen/cssc/transforms"
1617
"golang.org/x/sync/errgroup"
1718
)
@@ -42,7 +43,7 @@ func newCompilation(opts Options) *compilation {
4243
result: newResult(),
4344
reporter: logging.DefaultReporter,
4445
transforms: opts.Transforms,
45-
resolver: &NodeResolver{},
46+
resolver: &resolver.NodeResolver{},
4647
}
4748

4849
if opts.Reporter != nil {

resolver.go

Lines changed: 1 addition & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,159 +1,8 @@
11
package cssc
22

3-
import (
4-
"encoding/json"
5-
"errors"
6-
"io/ioutil"
7-
"os"
8-
"path/filepath"
9-
"strings"
10-
11-
"github.com/samsarahq/go/oops"
12-
)
13-
14-
// ErrNotFound is returned when the resolver cannot resolve a path.
15-
var ErrNotFound = errors.New("could not resolve css module")
16-
173
// Resolver implements a method of resolving an import spec (e.g. @import "test.css")
18-
// into a
4+
// into a path on the filesystem.
195
type Resolver interface {
206
// Resolve spec relative to from.
217
Resolve(spec, fromDir string) (path string, err error)
228
}
23-
24-
// NodeResolver implements the default node import resolution strategy. See
25-
// https://www.typescriptlang.org/docs/handbook/module-resolution.html.
26-
//
27-
// When resolving node_modules, the resolver will use the style attribute in
28-
// package.json for resolution.
29-
type NodeResolver struct {
30-
// BaseURL is the root directory of the project. It serves
31-
// the same purpose as baseUrl in tsconfig.json. If the value is relative,
32-
// it will be resolved against the current working directory.
33-
BaseURL string
34-
}
35-
36-
// Resolve implements Resolver.
37-
func (r *NodeResolver) Resolve(spec, fromDir string) (string, error) {
38-
if isRelative := strings.HasPrefix(spec, "../") || strings.HasPrefix(spec, "./") || strings.HasPrefix(spec, "/"); isRelative {
39-
path := filepath.Join(fromDir, spec)
40-
if res, err := r.resolve(path); err != nil {
41-
return "", oops.Wrapf(err, "could not resolve %s relative to %s", spec, fromDir)
42-
} else {
43-
return res, nil
44-
}
45-
}
46-
47-
// For non-relative imports, first try resolving against baseUrl.
48-
if r.BaseURL != "" {
49-
if res, err := r.resolve(filepath.Join(r.BaseURL, spec)); err == nil {
50-
return res, nil
51-
}
52-
}
53-
54-
// Lastly, try looking through node_modules.
55-
res, err := r.resolveFromNodeModules(spec, fromDir)
56-
if err != nil {
57-
return "", oops.Wrapf(ErrNotFound, "could not resolve absolute path %s from %s", spec, fromDir)
58-
}
59-
60-
return res, nil
61-
}
62-
63-
type packageJSON struct {
64-
Style string `json:"style"`
65-
}
66-
67-
// resolve attempts to resolve given absolute path as a file, then
68-
// as a package folder, then as a folder with an index.
69-
func (r *NodeResolver) resolve(absPath string) (string, error) {
70-
// Attempt to resolve first as a file.
71-
info, err := os.Stat(absPath)
72-
if err != nil {
73-
if os.IsNotExist(err) {
74-
// If it doesn't exist, try to resolve with an extension.
75-
withExtension := absPath + ".css"
76-
info, err := os.Stat(withExtension)
77-
if err != nil {
78-
if os.IsNotExist(err) {
79-
return "", oops.Wrapf(ErrNotFound, "could not resolve as file: %s or %s", absPath, withExtension)
80-
}
81-
return "", oops.Wrapf(err, "failure during resolution")
82-
}
83-
84-
if info.IsDir() {
85-
return "", oops.Wrapf(ErrNotFound, "%s exists, but is directory", withExtension)
86-
}
87-
88-
return withExtension, nil
89-
}
90-
91-
return "", oops.Wrapf(err, "failure during resolution")
92-
}
93-
94-
if !info.IsDir() {
95-
return absPath, nil
96-
}
97-
98-
// Otherwise, try to resolve as a directory.
99-
path, err := r.resolveAsDir(absPath)
100-
if err == nil {
101-
return path, nil
102-
}
103-
104-
return "", oops.Wrapf(err, "could not resolve path: %s", absPath)
105-
}
106-
107-
// resolveAsDir takes a directory path and resolves its css entry point.
108-
func (r *NodeResolver) resolveAsDir(path string) (string, error) {
109-
pkgPath := filepath.Join(path, "package.json")
110-
pkg, err := ioutil.ReadFile(pkgPath)
111-
if err != nil {
112-
// If there is no package.json, then try resolving an index.css file.
113-
indexPath := filepath.Join(path, "index.css")
114-
if info, err := os.Stat(indexPath); err != nil || info.IsDir() {
115-
if os.IsNotExist(err) {
116-
return "", oops.Wrapf(ErrNotFound, "could not resolve as directory: %s", path)
117-
}
118-
return "", oops.Wrapf(err, "failure during resolution")
119-
}
120-
121-
return indexPath, nil
122-
}
123-
124-
// Look for the style attribute in the package.json.
125-
var pkgContent packageJSON
126-
if err := json.Unmarshal(pkg, &pkgContent); err != nil {
127-
return "", oops.Wrapf(err, "failed to read package.json: %s", pkgPath)
128-
}
129-
130-
if pkgContent.Style == "" {
131-
return "", oops.Wrapf(ErrNotFound, "package.json exists, but has no style attribute: %s", pkgPath)
132-
}
133-
134-
stylePath := filepath.Join(path, pkgContent.Style)
135-
if info, err := os.Stat(stylePath); err != nil || info.IsDir() {
136-
if os.IsNotExist(err) {
137-
return "", oops.Wrapf(ErrNotFound, "package.json has style attribute, but it cannot be resolved: %s (to %s)", pkgContent.Style, stylePath)
138-
}
139-
return "", oops.Wrapf(err, "failure during resolution")
140-
}
141-
142-
return stylePath, nil
143-
}
144-
145-
// resolveAsNodeModule walks directories from fromDir to find node_modules paths.
146-
func (r *NodeResolver) resolveFromNodeModules(module, fromDir string) (string, error) {
147-
currentDir := fromDir
148-
for currentDir != "/" {
149-
modulePkgPath := filepath.Join(currentDir, "node_modules", module)
150-
151-
if res, err := r.resolve(modulePkgPath); err == nil {
152-
return res, nil
153-
}
154-
155-
currentDir = filepath.Dir(currentDir)
156-
}
157-
158-
return "", oops.Wrapf(ErrNotFound, "could not find absolute path in node_modules")
159-
}

resolver/node_resolver.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package resolver
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"io/ioutil"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
11+
"github.com/samsarahq/go/oops"
12+
)
13+
14+
// ErrNotFound is returned when the resolver cannot resolve a path.
15+
var ErrNotFound = errors.New("could not resolve css module")
16+
17+
// NodeResolver implements the default node import resolution strategy. See
18+
// https://www.typescriptlang.org/docs/handbook/module-resolution.html.
19+
//
20+
// When resolving node_modules, the resolver will use the style attribute in
21+
// package.json for resolution.
22+
type NodeResolver struct {
23+
// BaseURL is the root directory of the project. It serves
24+
// the same purpose as baseUrl in tsconfig.json. If the value is relative,
25+
// it will be resolved against the current working directory.
26+
BaseURL string
27+
}
28+
29+
// Resolve implements Resolver.
30+
func (r *NodeResolver) Resolve(spec, fromDir string) (string, error) {
31+
if isRelative := strings.HasPrefix(spec, "../") || strings.HasPrefix(spec, "./") || strings.HasPrefix(spec, "/"); isRelative {
32+
path := filepath.Join(fromDir, spec)
33+
if res, err := r.resolve(path); err != nil {
34+
return "", oops.Wrapf(err, "could not resolve %s relative to %s", spec, fromDir)
35+
} else {
36+
return res, nil
37+
}
38+
}
39+
40+
// For non-relative imports, first try resolving against baseUrl.
41+
if r.BaseURL != "" {
42+
if res, err := r.resolve(filepath.Join(r.BaseURL, spec)); err == nil {
43+
return res, nil
44+
}
45+
}
46+
47+
// Lastly, try looking through node_modules.
48+
res, err := r.resolveFromNodeModules(spec, fromDir)
49+
if err != nil {
50+
return "", oops.Wrapf(ErrNotFound, "could not resolve absolute path %s from %s", spec, fromDir)
51+
}
52+
53+
return res, nil
54+
}
55+
56+
type packageJSON struct {
57+
Style string `json:"style"`
58+
}
59+
60+
// resolve attempts to resolve given absolute path as a file, then
61+
// as a package folder, then as a folder with an index.
62+
func (r *NodeResolver) resolve(absPath string) (string, error) {
63+
// Attempt to resolve first as a file.
64+
info, err := os.Stat(absPath)
65+
if err != nil {
66+
if os.IsNotExist(err) {
67+
// If it doesn't exist, try to resolve with an extension.
68+
withExtension := absPath + ".css"
69+
info, err := os.Stat(withExtension)
70+
if err != nil {
71+
if os.IsNotExist(err) {
72+
return "", oops.Wrapf(ErrNotFound, "could not resolve as file: %s or %s", absPath, withExtension)
73+
}
74+
return "", oops.Wrapf(err, "failure during resolution")
75+
}
76+
77+
if info.IsDir() {
78+
return "", oops.Wrapf(ErrNotFound, "%s exists, but is directory", withExtension)
79+
}
80+
81+
return withExtension, nil
82+
}
83+
84+
return "", oops.Wrapf(err, "failure during resolution")
85+
}
86+
87+
if !info.IsDir() {
88+
return absPath, nil
89+
}
90+
91+
// Otherwise, try to resolve as a directory.
92+
path, err := r.resolveAsDir(absPath)
93+
if err == nil {
94+
return path, nil
95+
}
96+
97+
return "", oops.Wrapf(err, "could not resolve path: %s", absPath)
98+
}
99+
100+
// resolveAsDir takes a directory path and resolves its css entry point.
101+
func (r *NodeResolver) resolveAsDir(path string) (string, error) {
102+
pkgPath := filepath.Join(path, "package.json")
103+
pkg, err := ioutil.ReadFile(pkgPath)
104+
if err != nil {
105+
// If there is no package.json, then try resolving an index.css file.
106+
indexPath := filepath.Join(path, "index.css")
107+
if info, err := os.Stat(indexPath); err != nil || info.IsDir() {
108+
if os.IsNotExist(err) {
109+
return "", oops.Wrapf(ErrNotFound, "could not resolve as directory: %s", path)
110+
}
111+
return "", oops.Wrapf(err, "failure during resolution")
112+
}
113+
114+
return indexPath, nil
115+
}
116+
117+
// Look for the style attribute in the package.json.
118+
var pkgContent packageJSON
119+
if err := json.Unmarshal(pkg, &pkgContent); err != nil {
120+
return "", oops.Wrapf(err, "failed to read package.json: %s", pkgPath)
121+
}
122+
123+
if pkgContent.Style == "" {
124+
return "", oops.Wrapf(ErrNotFound, "package.json exists, but has no style attribute: %s", pkgPath)
125+
}
126+
127+
stylePath := filepath.Join(path, pkgContent.Style)
128+
if info, err := os.Stat(stylePath); err != nil || info.IsDir() {
129+
if os.IsNotExist(err) {
130+
return "", oops.Wrapf(ErrNotFound, "package.json has style attribute, but it cannot be resolved: %s (to %s)", pkgContent.Style, stylePath)
131+
}
132+
return "", oops.Wrapf(err, "failure during resolution")
133+
}
134+
135+
return stylePath, nil
136+
}
137+
138+
// resolveAsNodeModule walks directories from fromDir to find node_modules paths.
139+
func (r *NodeResolver) resolveFromNodeModules(module, fromDir string) (string, error) {
140+
currentDir := fromDir
141+
for currentDir != "/" {
142+
modulePkgPath := filepath.Join(currentDir, "node_modules", module)
143+
144+
if res, err := r.resolve(modulePkgPath); err == nil {
145+
return res, nil
146+
}
147+
148+
currentDir = filepath.Dir(currentDir)
149+
}
150+
151+
return "", oops.Wrapf(ErrNotFound, "could not find absolute path in node_modules")
152+
}

resolver_test.go renamed to resolver/node_resolver_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
package cssc_test
1+
package resolver_test
22

33
import (
44
"path/filepath"
55
"testing"
66

7-
"github.com/stephen/cssc"
7+
"github.com/stephen/cssc/resolver"
88
"github.com/stretchr/testify/assert"
99
"github.com/stretchr/testify/require"
1010
)
1111

1212
func TestResolver_Relative(t *testing.T) {
13-
testdata, err := filepath.Abs("testdata/resolver/")
13+
testdata, err := filepath.Abs("testdata/")
1414
require.NoError(t, err)
1515

16-
r := cssc.NodeResolver{}
16+
r := resolver.NodeResolver{}
1717

1818
result, err := r.Resolve("./case-1.css", testdata)
1919
assert.NoError(t, err)
@@ -61,10 +61,10 @@ func TestResolver_Relative(t *testing.T) {
6161
}
6262

6363
func TestResolver_Absolute_WithBaseURL(t *testing.T) {
64-
testdata, err := filepath.Abs("testdata/resolver/")
64+
testdata, err := filepath.Abs("testdata/")
6565
require.NoError(t, err)
6666

67-
r := cssc.NodeResolver{BaseURL: testdata}
67+
r := resolver.NodeResolver{BaseURL: testdata}
6868

6969
result, err := r.Resolve("case-1.css", testdata)
7070
assert.NoError(t, err)
@@ -92,10 +92,10 @@ func TestResolver_Absolute_WithBaseURL(t *testing.T) {
9292
}
9393

9494
func TestResolver_Absolute(t *testing.T) {
95-
testdata, err := filepath.Abs("testdata/resolver/nested/1/2/")
95+
testdata, err := filepath.Abs("testdata/nested/1/2/")
9696
require.NoError(t, err)
9797

98-
r := cssc.NodeResolver{}
98+
r := resolver.NodeResolver{}
9999

100100
result, err := r.Resolve("case-4", testdata)
101101
assert.NoError(t, err)
File renamed without changes.

0 commit comments

Comments
 (0)