|
1 | 1 | import type { types as t } from "@marko/compiler"; |
2 | 2 | import type { TagDefinition, TaglibLookup } from "@marko/compiler/babel-utils"; |
| 3 | +import path from "path"; |
3 | 4 | import { relativeImportPath } from "relative-import-path"; |
4 | 5 | import type TS from "typescript/lib/tsserverlibrary"; |
5 | 6 |
|
@@ -52,6 +53,7 @@ const REG_OBJECT_PROPERTY = /^[_$a-z][_$a-z0-9]*$/i; |
52 | 53 | // Match https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-path- and https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html#ts-check |
53 | 54 | const REG_COMMENT_PRAGMA = /\/\/(?:\s*@ts-|\/\s*<)/y; |
54 | 55 | const REG_TAG_NAME_IDENTIFIER = /^[A-Z][a-zA-Z0-9_$]+$/; |
| 56 | +const REG_NODE_MODULES = /[\\/]node_modules[\\/]/; |
55 | 57 | const IF_TAG_ALTERNATES = new WeakMap<IfTag, IfTagAlternates>(); |
56 | 58 | const TAG_ID = new WeakMap<Node.Tag, number>(); |
57 | 59 | const RENDER_VAR = new WeakMap<Node.Tag, string>(); |
@@ -2182,12 +2184,42 @@ function isValueAttribute( |
2182 | 2184 |
|
2183 | 2185 | function resolveTagImport(from: string, def: TagDefinition | undefined) { |
2184 | 2186 | const filename = resolveTagFile(def); |
2185 | | - if (filename) { |
2186 | | - // `from` is parsed.filename which is already normalized, but the taglib |
2187 | | - // provided path must use native separators too or relativeImportPath |
2188 | | - // falls back to returning the absolute path. |
2189 | | - return from ? relativeImportPath(from, normalizePath(filename)) : filename; |
2190 | | - } |
| 2187 | + if (!def || !filename) return; |
| 2188 | + if (!from) return filename; |
| 2189 | + |
| 2190 | + // `from` is parsed.filename which is already normalized, but the taglib |
| 2191 | + // provided path must use native separators too or relativeImportPath |
| 2192 | + // falls back to returning the absolute path. |
| 2193 | + const to = normalizePath(filename); |
| 2194 | + return packageImportPath(from, def, to) || relativeImportPath(from, to); |
| 2195 | +} |
| 2196 | + |
| 2197 | +/** |
| 2198 | + * A tag installed into `node_modules` is imported through the name its package was |
| 2199 | + * resolved by, since its path is a realpath which may not be importable at all. |
| 2200 | + */ |
| 2201 | +function packageImportPath(from: string, def: TagDefinition, filename: string) { |
| 2202 | + const { packageName, packageRoot } = def; |
| 2203 | + if (!packageName || !packageRoot || !REG_NODE_MODULES.test(filename)) return; |
| 2204 | + |
| 2205 | + // Within the package we stay relative, both because it always resolves and |
| 2206 | + // because a self reference would only work if the package exports the tag. |
| 2207 | + if (!isWithin(packageRoot, filename) || isWithin(packageRoot, from)) return; |
| 2208 | + |
| 2209 | + const subPath = path.relative(packageRoot, filename); |
| 2210 | + return `${packageName}/${subPath.split(path.sep).join("/")}`; |
| 2211 | +} |
| 2212 | + |
| 2213 | +function isWithin(dir: string, filename: string) { |
| 2214 | + const rel = path.relative(dir, filename); |
| 2215 | + // `path.relative` walks out of the dir with `..`, or returns an absolute path |
| 2216 | + // when it cannot relate the two at all, eg across windows drives. |
| 2217 | + return ( |
| 2218 | + !!rel && |
| 2219 | + rel !== ".." && |
| 2220 | + !rel.startsWith(`..${path.sep}`) && |
| 2221 | + !path.isAbsolute(rel) |
| 2222 | + ); |
2191 | 2223 | } |
2192 | 2224 |
|
2193 | 2225 | function resolveTagFile(def: TagDefinition | undefined): string | undefined { |
|
0 commit comments