Files
anissah/user/themes/anissabensalah/node_modules/csscomb/lib/csscomb.js
T
2018-05-28 13:26:25 +02:00

322 lines
9.7 KiB
JavaScript

var Comb = require('csscomb-core');
var gonzales = require('gonzales-pe');
var fs = require('fs');
var path = require('path');
/**
* Converts CSS string to AST.
*
* @param {String} text CSS string
* @param {String} [syntax] Syntax name (e.g., `scss`)
* @param {String} [filename]
* @returns {Array} AST
*/
function cssToAST(text, syntax, filename) {
var string = JSON.stringify;
var fileInfo = filename ? ' at ' + filename : '';
var tree;
try {
tree = gonzales.parse(text, { syntax: syntax });
} catch (e) {
throw new Error('Parsing error' + fileInfo + ': ' + e.message);
}
// TODO: When can tree be undefined? <tg>
if (typeof tree === 'undefined') {
throw new Error('Undefined tree' + fileInfo + ': ' + string(text) + ' => ' + string(tree));
}
return tree;
}
/**
* Gets option's data needed for detection
*
* @param {String} optionName
* @returns {Object} Object with option's name, link to `detect()` method
* and default value for the case when nothing can be detected
*/
function getHandler(optionName) {
var option = require('./options/' + optionName);
if (!option.detect) throw new Error('Option does not have `detect()` method.');
return {
name: option.name,
detect: option.detect,
detectDefault: option.detectDefault
};
}
/**
* Processes tree node and detects options.
*
* @param {Array} node Tree node
* @param {Number} level Indent level
* @param {Object} handler Object with option's data
* @param {Object} detectedOptions
*/
function detectInNode(node, level, handler, detectedOptions) {
node.map(function(tree) {
var detected = handler.detect(tree);
var variants = detectedOptions[handler.name];
if (typeof detected === 'object') {
variants.push.apply(variants, detected);
} else if (typeof detected !== 'undefined') {
variants.push(detected);
}
//if (nodeType === 'atrulers' || nodeType === 'block') level++;
});
}
/**
* Processes tree and detects options.
*
* @param {Array} tree
* @param {Array} handlers List of options that we should look for
* @returns {Object} Map with detected options and all variants of possible
* values
*/
function detectInTree(tree, handlers) {
var detectedOptions = {};
// We walk across complete tree for each handler,
// because we need strictly maintain order in which handlers work,
// despite fact that handlers work on different level of the tree.
handlers.forEach(function(handler) {
detectedOptions[handler.name] = [];
// TODO: Pass all parameters as one object? <tg>
detectInNode(tree, 0, handler, detectedOptions);
});
return detectedOptions;
}
/**
* Gets the detected options.
*
* @param {Object} detected
* @param {Array} handlers
* @returns {Object}
*/
function getDetectedOptions(detected, handlers) {
var options = {};
Object.keys(detected).forEach(function(option) {
// List of all the detected variants from the stylesheet for the given option:
var values = detected[option];
var i;
if (!values.length) {
// If there are no values for the option, check if there is a default one:
for (i = handlers.length; i--;) {
if (handlers[i].name === option &&
handlers[i].detectDefault !== undefined) {
options[option] = handlers[i].detectDefault;
break;
}
}
} else if (values.length === 1) {
options[option] = values[0];
} else {
// If there are more than one value for the option, find the most popular one;
// `variants` would be populated with the popularity for different values.
var variants = {};
var bestGuess = null;
var maximum = 0;
for (i = values.length; i--;) {
var currentValue = values[i];
// Count the current value:
if (variants[currentValue]) {
variants[currentValue]++;
} else {
variants[currentValue] = 1;
}
// If the current variant is the most popular one, treat
// it as the best guess:
if (variants[currentValue] >= maximum) {
maximum = variants[currentValue];
bestGuess = currentValue;
}
}
if (bestGuess !== null) {
options[option] = bestGuess;
}
}
});
return options;
}
/**
* Starts Code Style processing process.
*
* @param {String|Object} config
* @constructor
* @name CSScomb
*/
var CSScomb = function(config) {
var options = fs.readdirSync(__dirname + '/options').map(function(option) {
return require('./options/' + option);
});
var comb = new Comb(options, 'css', 'less', 'scss', 'sass');
// If config was passed, configure:
if (typeof config === 'string') {
config = CSScomb.getConfig(config);
}
if (typeof config === 'object') {
comb.configure(config);
}
return comb;
};
/**
* STATIC METHODS
* Methods that can be called without creating an instance:
* - getConfig;
* - getCustomConfig;
* - getCustomConfigPath;
* - detectInFile;
* - detectInString.
* For example: `CSScomb.getConfig('zen')`
*/
/**
* Gets one of configuration files from configs' directory.
*
* @param {String} name Config's name, e.g. 'yandex'
* @returns {Object} Configuration object
*/
CSScomb.getConfig = function getConfig(name) {
var DEFAULT_CONFIG_NAME = 'csscomb';
name = name || DEFAULT_CONFIG_NAME;
if (typeof name !== 'string') {
throw new Error('Config name must be a string.');
}
var CONFIG_DIR_PATH = '../config';
var availableConfigsNames = fs.readdirSync(__dirname + '/' + CONFIG_DIR_PATH)
.map(function(configFileName) {
return configFileName.split('.')[0]; // strip file extension(s)
});
if (availableConfigsNames.indexOf(name) < 0) {
var configsNamesAsString = availableConfigsNames
.map(function(configName) {
return '\'' + configName + '\'';
})
.join(', ');
throw new Error('"' + name + '" is not a valid config name. Try one of ' +
'the following: ' + configsNamesAsString + '.');
}
return require(CONFIG_DIR_PATH + '/' + name + '.json');
};
/**
* Gets configuration from provided config path or looks for it in common
* places.
*
* @param {String} [configPath]
* @returns {Object|null}
*/
CSScomb.getCustomConfig = function getCustomConfig(configPath) {
var config;
configPath = configPath || CSScomb.getCustomConfigPath();
try {
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
} catch (e) {
config = null;
}
return config;
};
/**
* Looks for a config file: recursively from current (process) directory
* up to $HOME dir
* If no custom config file is found, return `null`.
*
* @param {String} [configPath]
* @returns {String | null}
*/
CSScomb.getCustomConfigPath = function getCustomConfigPath(configPath) {
var HOME = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
configPath = configPath || path.join(process.cwd(), '.csscomb.json');
// If we've finally found a config, return its path:
if (fs.existsSync(configPath)) return fs.realpathSync(configPath);
// If we are in HOME dir already and yet no config file, return a default
// one from our package.
// If project is located not under HOME, compare to root instead.
// Since there appears to be no good way to get root path in
// Windows, assume that if current dir has no parent dir, we're in
// root.
var dirname = path.dirname(configPath);
var parentDirname = path.dirname(dirname);
if (dirname === HOME || dirname === parentDirname) return null;
// If there is no config in this directory, go one level up and look for
// a config there:
configPath = path.join(parentDirname, '.csscomb.json');
return CSScomb.getCustomConfigPath(configPath);
};
/**
* Detects the options in the given file
*
* @param {String} path Path to the stylesheet
* @param {Array} options List of options to detect
* @returns {Object} Detected options
*/
CSScomb.detectInFile = function detectInFile(path, options) {
var stylesheet = fs.readFileSync(path, 'utf8');
return CSScomb.detectInString(stylesheet, options);
};
/**
* Detects the options in the given string
*
* @param {String} text Stylesheet
* @param {Array} options List of options to detect
* @returns {Object} Detected options
*/
CSScomb.detectInString = function detectInString(text, options) {
var result;
var handlers = [];
if (!text) return text;
var optionNames = fs.readdirSync(__dirname + '/options');
optionNames.forEach(function(option) {
option = option.slice(0, -3);
if (options && options.indexOf(option) < 0) return;
try {
handlers.push(getHandler(option));
} catch (e) {
console.warn('\nFailed to load "%s" option:\n%s', option, e.message);
}
});
var tree = cssToAST(text);
var detectedOptions = detectInTree(tree, handlers);
result = getDetectedOptions(detectedOptions, handlers);
// Handle conflicting options with spaces around braces:
var blockIndent = result['block-indent'];
var spaceAfterOpeningBrace = result['space-after-opening-brace'];
if (typeof blockIndent === 'string' &&
spaceAfterOpeningBrace &&
spaceAfterOpeningBrace.indexOf('\n') > -1) {
result['space-after-opening-brace'] = '\n';
}
return result;
};
module.exports = CSScomb;