eyeglass
Version:
Sass modules for npm.
143 lines (129 loc) • 4.18 kB
text/typescript
import {
format as formatPath,
join as joinPaths,
parse as parsePath,
ParsedPath,
sep as PATH_SEPARATOR,
} from "path";
const SASS_FILE_EXT = /^\.(s[ac]|c)ss$/
/**
* provides an interface for expanding a given URI to valid import locations
*
* @constructor
* @param {String} uri - the base URI to be expanded
*/
export class NameExpander {
uri: string;
_possibleFiles: Set<string>;
locations: Array<string>;
constructor(uri: string) {
// normalize the uri
this.uri = normalizeURI(uri);
this.locations = new Array<string>();
// the collection of possible files
this._possibleFiles = new Set();
}
get files(): Set<string> {
if (this._possibleFiles.size === 0) {
this.calculatePossibleFiles();
}
return this._possibleFiles;
}
/**
* given a location, expands the collection of possible file imports
*
* @param {String} location - the location path the expand the URI against
*/
addLocation(location: string): void {
/* istanbul ignore else - defensive conditional, don't care about else-case */
if (!location || location === "stdin") {
return;
}
this.locations.push(location);
if (this._possibleFiles.size > 0) {
this._possibleFiles = new Set<string>();
}
}
private calculatePossibleFiles(): void {
for (let location of this.locations) {
// get the full path to the uri
let fullLocation = joinPaths(location, this.uri);
let path = parsePath(fullLocation);
let indexPath = getIndexPath(path)
this._possibleFiles.add(fileVariant(path, "_", ".scss"));
if (indexPath) {
this._possibleFiles.add(fileVariant(indexPath, "_", ".scss"));
}
this._possibleFiles.add(fileVariant(path, null, ".scss"));
if (indexPath) {
this._possibleFiles.add(fileVariant(indexPath, null, ".scss"));
}
this._possibleFiles.add(fileVariant(path, null, ".sass"));
this._possibleFiles.add(fileVariant(path, null, ".css"));
this._possibleFiles.add(fileVariant(path, "_", ".sass"));
this._possibleFiles.add(fileVariant(path, "_", ".css"));
if (indexPath) {
this._possibleFiles.add(fileVariant(indexPath, null, ".sass"));
this._possibleFiles.add(fileVariant(indexPath, null, ".css"));
this._possibleFiles.add(fileVariant(indexPath, "_", ".sass"));
this._possibleFiles.add(fileVariant(indexPath, "_", ".css"));
}
}
}
}
function getIndexPath(
path: ParsedPath
): ParsedPath | null {
path = Object.create(path);
if (path.name === "index" || path.name === "_index" || (path.ext && SASS_FILE_EXT.test(path.ext))) {
return null;
}
path.dir = joinPaths(path.dir, path.name);
path.name = "index"
path.base = "index"
return path;
}
/* This function returns a variant if it's allowed for the given path.
* otherwise it returns as much of the specified variant as is allowed.
* That means that for some paths, this returns the same output for different
* variant arguments. It is expected that the caller will deduplicate the
* returned values.
*
* It will:
* - treat non-sass extensions as belonging to the base name.
*
* It will not:
* - change an explicit sass extension to a different sass extension
* - remove a partial prefix (underscore)
* - Add a partial prefix to a file that already has a partial prefix.
*
*/
function fileVariant(
path: ParsedPath,
partial: "_" | null,
extension: ".scss" | ".sass" | ".css"
): string {
path = Object.create(path);
if (path.ext && !SASS_FILE_EXT.test(path.ext)) {
path.base = path.base + path.ext;
path.name = path.name + path.ext;
path.ext = "";
}
if (!path.ext) {
path.ext = extension;
}
if (partial && !path.name.startsWith(partial)) {
path.name = partial + path.name;
}
path.base = path.name + path.ext;
return formatPath(path);
}
/**
* normalizes the URI path
* @param {String} uri - the URI to normalize
* @returns {String} the normalized URI
*/
function normalizeURI(uri: string): string {
// update the separator to use the OS separator
return uri.replace(/\//g, PATH_SEPARATOR);
}