Skip to content

Commit

Permalink
polish
Browse files Browse the repository at this point in the history
  • Loading branch information
aeschli committed Jun 24, 2024
1 parent 62c58c3 commit ade0b03
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 173 deletions.
8 changes: 4 additions & 4 deletions src/cssLanguageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export namespace ClientCapabilities {

export interface LanguageServiceOptions {
/**
* Unless set to false, the default CSS data provider will be used
* Unless set to false, the default CSS data provider will be used
* along with the providers from customDataProviders.
* Defaults to true.
*/
Expand Down Expand Up @@ -284,7 +284,7 @@ export interface FileStat {
export interface FileSystemProvider {
stat(uri: DocumentUri): Promise<FileStat>;
readDirectory?(uri: DocumentUri): Promise<[string, FileType][]>;
getContent?(uri: DocumentUri, encoding?: BufferEncoding): Promise<string>;
getContent?(uri: DocumentUri, encoding?: string): Promise<string>;
}

export interface CSSFormatConfiguration {
Expand All @@ -306,11 +306,11 @@ export interface CSSFormatConfiguration {
preserveNewLines?: boolean;
/** maximum number of line breaks to be preserved in one chunk. Default: unlimited */
maxPreserveNewLines?: number;
/** maximum amount of characters per line (0/undefined = disabled). Default: disabled. */
/** maximum amount of characters per line (0/undefined = disabled). Default: disabled. */
wrapLineLength?: number;
/** add indenting whitespace to empty lines. Default: false */
indentEmptyLines?: boolean;

/** @deprecated Use newlineBetweenSelectors instead*/
selectorSeparatorNewline?: boolean;

Expand Down
94 changes: 2 additions & 92 deletions src/services/cssNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,13 +392,6 @@ 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:')) {
return this.resolvePkgModulePath(target, documentUri, documentContext);
}

const ref = await this.mapReference(documentContext.resolveReference(target, documentUri), isRawLink);

// Following [less-loader](https://github.com/webpack-contrib/less-loader#imports)
Expand Down Expand Up @@ -439,7 +432,7 @@ export class CSSNavigation {
return ref;
}

private async resolvePathToModule(_moduleName: string, documentFolderUri: string, rootFolderUri: string | undefined): Promise<string | undefined> {
protected async resolvePathToModule(_moduleName: string, documentFolderUri: string, rootFolderUri: string | undefined): Promise<string | undefined> {
// resolve the module relative to the document. We can't use `require` here as the code is webpacked.

const packPath = joinPath(documentFolderUri, 'node_modules', _moduleName, 'package.json');
Expand All @@ -451,89 +444,6 @@ export class CSSNavigation {
return undefined;
}

private async resolvePkgModulePath(target: string, documentUri: string, documentContext: DocumentContext): Promise<string | undefined> {
const bareTarget = target.replace('pkg:', '');
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;
}
}
}
}
}
return undefined;
}

protected async fileExists(uri: string): Promise<boolean> {
if (!this.fileSystemProvider) {
Expand Down Expand Up @@ -633,7 +543,7 @@ function toTwoDigitHex(n: number): string {
return r.length !== 2 ? '0' + r : r;
}

function getModuleNameFromPath(path: string) {
export function getModuleNameFromPath(path: string) {
const firstSlash = path.indexOf('/');
if (firstSlash === -1) {
return '';
Expand Down
103 changes: 101 additions & 2 deletions src/services/scssNavigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
'use strict';

import { CSSNavigation } from './cssNavigation';
import { CSSNavigation, getModuleNameFromPath } from './cssNavigation';
import { FileSystemProvider, DocumentContext, FileType, DocumentUri } from '../cssLanguageTypes';
import * as nodes from '../parser/cssNodes';
import { URI, Utils } from 'vscode-uri';
import { startsWith } from '../utils/strings';
import { convertSimple2RegExpPattern, startsWith } from '../utils/strings';
import { dirname, joinPath } from '../utils/resources';

export class SCSSNavigation extends CSSNavigation {
constructor(fileSystemProvider: FileSystemProvider | undefined) {
Expand Down Expand Up @@ -39,8 +40,106 @@ export class SCSSNavigation extends CSSNavigation {
if (startsWith(target, 'sass:')) {
return undefined; // sass library
}
// 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:')) {
return this.resolvePkgModulePath(target, documentUri, documentContext);
}
return super.resolveReference(target, documentUri, documentContext, isRawLink);
}

private async resolvePkgModulePath(target: string, documentUri: string, documentContext: DocumentContext): Promise<string | undefined> {
const bareTarget = target.replace('pkg:', '');
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) {
return undefined;
}
// 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(joinPath(modulePath, 'package.json'));
if (!packageJsonContent) {
return undefined;
}
let packageJson: {
style?: string;
sass?: string;
exports?: Record<string, string | Record<string, string>>
};
try {
packageJson = JSON.parse(packageJsonContent);
} catch (e) {
// problems parsing package.json
return undefined;
}

const subpath = bareTarget.substring(moduleName.length + 1);
if (packageJson.exports) {
if (!subpath) {
const dotExport = packageJson.exports['.'];
// look for the default/index export
// @ts-expect-error If ['.'] is a string this just produces undefined
const entry = dotExport && (dotExport['sass'] || dotExport['style'] || dotExport['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(convertSimple2RegExpPattern(maybePattern.replace('.scss', '')).replace(/\.\*/g, '(.*)'));
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;
}
}
return undefined;

}

}

function toPathVariations(target: string): DocumentUri[] {
Expand Down
Loading

0 comments on commit ade0b03

Please sign in to comment.