-
Notifications
You must be signed in to change notification settings - Fork 182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add support for Sass pkg:
importers
#384
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
lib/ | ||
node_modules/ | ||
!src/test/scss/linkFixture/pkgImport/node_modules/ | ||
coverage/ | ||
.nyc_output/ | ||
npm-debug.log |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -392,6 +392,92 @@ export class CSSNavigation { | |
return this.mapReference(await this.resolveModuleReference(target, documentUri, documentContext), isRawLink); | ||
} | ||
|
||
// Following the [sass package importer](https://github.com/sass/sass/blob/f6832f974c61e35c42ff08b3640ff155071a02dd/js-api-doc/importer.d.ts#L349), | ||
// look for the `exports` field of the module and any `sass`, `style` or `default` that matches the import. | ||
// If it's only `pkg:module`, also look for `sass` and `style` on the root of package.json. | ||
if (target.startsWith('pkg:')) { | ||
const bareTarget = target.replace('pkg:', ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this be extracted in a method ? |
||
const moduleName = bareTarget.includes('/') ? getModuleNameFromPath(bareTarget) : bareTarget; | ||
const rootFolderUri = documentContext.resolveReference('/', documentUri); | ||
const documentFolderUri = dirname(documentUri); | ||
const modulePath = await this.resolvePathToModule(moduleName, documentFolderUri, rootFolderUri); | ||
if (modulePath) { | ||
const packageJsonPath = `${modulePath}/package.json`; | ||
if (packageJsonPath) { | ||
// Since submodule exports import strings don't match the file system, | ||
// we need the contents of `package.json` to look up the correct path. | ||
let packageJsonContent = await this.getContent(packageJsonPath); | ||
if (packageJsonContent) { | ||
const packageJson: { | ||
style?: string; | ||
sass?: string; | ||
exports: Record<string, string | Record<string, string>> | ||
} = JSON.parse(packageJsonContent); | ||
|
||
const subpath = bareTarget.substring(moduleName.length + 1); | ||
if (packageJson.exports) { | ||
if (!subpath) { | ||
// look for the default/index export | ||
// @ts-expect-error If ['.'] is a string this just produces undefined | ||
const entry = packageJson.exports['.']['sass'] || packageJson.exports['.']['style'] || packageJson.exports['.']['default']; | ||
// the 'default' entry can be whatever, typically .js – confirm it looks like `scss` | ||
if (entry && entry.endsWith('.scss')) { | ||
const entryPath = joinPath(modulePath, entry); | ||
return entryPath; | ||
} | ||
} else { | ||
// The import string may be with or without .scss. | ||
// Likewise the exports entry. Look up both paths. | ||
// However, they need to be relative (start with ./). | ||
const lookupSubpath = subpath.endsWith('.scss') ? `./${subpath.replace('.scss', '')}` : `./${subpath}`; | ||
const lookupSubpathScss = subpath.endsWith('.scss') ? `./${subpath}` : `./${subpath}.scss`; | ||
const subpathObject = packageJson.exports[lookupSubpathScss] || packageJson.exports[lookupSubpath]; | ||
if (subpathObject) { | ||
// @ts-expect-error If subpathObject is a string this just produces undefined | ||
const entry = subpathObject['sass'] || subpathObject['styles'] || subpathObject['default']; | ||
// the 'default' entry can be whatever, typically .js – confirm it looks like `scss` | ||
if (entry && entry.endsWith('.scss')) { | ||
const entryPath = joinPath(modulePath, entry); | ||
return entryPath; | ||
} | ||
} else { | ||
// We have a subpath, but found no matches on direct lookup. | ||
// It may be a [subpath pattern](https://nodejs.org/api/packages.html#subpath-patterns). | ||
for (const [maybePattern, subpathObject] of Object.entries(packageJson.exports)) { | ||
if (!maybePattern.includes("*")) { | ||
continue; | ||
} | ||
// Patterns may also be without `.scss` on the left side, so compare without on both sides | ||
const re = new RegExp(maybePattern.replace('./', '\\.\/').replace('.scss', '').replace('*', '(.+)')); | ||
const match = re.exec(lookupSubpath); | ||
if (match) { | ||
// @ts-expect-error If subpathObject is a string this just produces undefined | ||
const entry = subpathObject['sass'] || subpathObject['styles'] || subpathObject['default']; | ||
// the 'default' entry can be whatever, typically .js – confirm it looks like `scss` | ||
if (entry && entry.endsWith('.scss')) { | ||
// The right-hand side of a subpath pattern is also a pattern. | ||
// Replace the pattern with the match from our regexp capture group above. | ||
const expandedPattern = entry.replace('*', match[1]); | ||
const entryPath = joinPath(modulePath, expandedPattern); | ||
return entryPath; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} else if (!subpath && (packageJson.sass || packageJson.style)) { | ||
// Fall back to a direct lookup on `sass` and `style` on package root | ||
const entry = packageJson.sass || packageJson.style; | ||
if (entry) { | ||
const entryPath = joinPath(modulePath, entry); | ||
return entryPath; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
const ref = await this.mapReference(documentContext.resolveReference(target, documentUri), isRawLink); | ||
|
||
// Following [less-loader](https://github.com/webpack-contrib/less-loader#imports) | ||
|
@@ -460,6 +546,18 @@ export class CSSNavigation { | |
} | ||
} | ||
|
||
protected async getContent(uri: string): Promise<string | null> { | ||
if (!this.fileSystemProvider || !this.fileSystemProvider.getContent) { | ||
return null; | ||
} | ||
try { | ||
return await this.fileSystemProvider.getContent(uri); | ||
} catch (err) { | ||
console.error(err); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should not print to console |
||
return null; | ||
} | ||
} | ||
|
||
} | ||
|
||
function getColorInformation(node: nodes.Node, document: TextDocument): ColorInformation | null { | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Matches the
RequestService
on the language server side