@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
170 lines • 7.48 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NAMING_CONVENTION = exports.CasingConvention = void 0;
exports.detectCasing = detectCasing;
exports.getMostUsedCasing = getMostUsedCasing;
exports.fixCasing = fixCasing;
exports.createNamingConventionQuickFixes = createNamingConventionQuickFixes;
const vertex_1 = require("../../dataflow/graph/vertex");
const dfg_get_symbol_refs_1 = require("../../dataflow/origin/dfg-get-symbol-refs");
const flowr_search_builder_1 = require("../../search/flowr-search-builder");
const assert_1 = require("../../util/assert");
const dfg_1 = require("../../util/mermaid/dfg");
const linter_format_1 = require("../linter-format");
const linter_tags_1 = require("../linter-tags");
var CasingConvention;
(function (CasingConvention) {
CasingConvention["CamelCase"] = "camelCase";
CasingConvention["PascalCase"] = "PascalCase";
CasingConvention["SnakeCase"] = "snake_case";
CasingConvention["ConstantCase"] = "CONSTANT_CASE";
CasingConvention["CamelSnakeCase"] = "camel_Snake_Case";
CasingConvention["PascalSnakeCase"] = "Pascal_Snake_Case";
CasingConvention["Unknown"] = "unknown";
})(CasingConvention || (exports.CasingConvention = CasingConvention = {}));
function detectCasing(identifier) {
if (identifier.trim() === '') {
return CasingConvention.Unknown;
}
const upper = identifier.toUpperCase();
const lower = identifier.toLowerCase();
const isAllUpper = identifier === upper;
const isAllLower = identifier === lower;
if (identifier.includes('_')) {
if (isAllUpper) { // CONSTANT_CASE
return CasingConvention.ConstantCase;
}
else if (isAllLower) { // snake_case
return CasingConvention.SnakeCase;
}
// Returns true if the letter after an _ is uppercase
function expectUpperAfterScore(identifier) {
for (let i = 0; i < identifier.length - 1; i++) {
if (identifier[i] === '_') {
if (identifier[i + 1] !== upper[i + 1]) {
return false;
}
}
}
return true;
}
if (identifier[0] === lower[0] && expectUpperAfterScore(identifier)) { // camel_Snake_Case
return CasingConvention.CamelSnakeCase;
}
else if (identifier[0] === upper[0] && expectUpperAfterScore(identifier)) { // Pascal_Snake_Case
return CasingConvention.PascalSnakeCase;
}
}
else {
if (identifier[0] === lower[0]) { // camelCase
return CasingConvention.CamelCase;
}
else if (identifier[0] === upper[0]) { // PascalCase
return CasingConvention.PascalCase;
}
}
return CasingConvention.Unknown;
}
function getMostUsedCasing(symbols) {
if (symbols.length === 0) {
return CasingConvention.Unknown;
}
const map = new Map();
for (const symbol of symbols) {
const o = map.get(symbol.detectedCasing) ?? 0;
map.set(symbol.detectedCasing, o + 1);
}
// Return element with most occurances
return [...map].reduce((p, c) => p[1] > c[1] ? p : c)[0];
}
function fixCasing(identifier, convention) {
const tokens = identifier.split(/(?=[A-Z])|_/).map(s => s.toLowerCase());
const firstUp = (s) => `${s[0].toUpperCase()}${s.substring(1)}`;
switch (convention) {
case CasingConvention.CamelCase: // camelCase
return `${tokens[0]}${tokens.slice(1).map(firstUp).join('')}`;
case CasingConvention.PascalCase: // PascalCase
return tokens.map(firstUp).join('');
case CasingConvention.SnakeCase: // snake_case
return tokens.join('_');
case CasingConvention.ConstantCase: // CONSTANT_CASE
return tokens.map(s => s.toUpperCase()).join('_');
case CasingConvention.CamelSnakeCase: // camel_Snake_Case
return `${tokens[0]}_${tokens.slice(1).map(firstUp).join('_')}`;
case CasingConvention.PascalSnakeCase: // Pascal_Snake_Case
return tokens.map(firstUp).join('_');
case CasingConvention.Unknown:
return identifier;
default:
(0, assert_1.assertUnreachable)(convention);
}
}
function createNamingConventionQuickFixes(graph, nodeId, replacement, conv) {
const refs = (0, dfg_get_symbol_refs_1.getAllRefsToSymbol)(graph, nodeId);
const idMap = graph.idMap;
if (refs === undefined || idMap === undefined) {
return undefined;
}
const result = [];
for (const ref of refs) {
const node = idMap.get(ref);
if (node === undefined) {
continue;
}
const range = node.info.fullRange;
if (range) {
// In case of a function call we only need to include the name, not the '()'
range[3] = range[1] + node.lexeme.length - 1;
result.push({
type: 'replace',
replacement: replacement,
description: `Rename to match naming convention ${conv}`,
range: range
});
}
}
return result.length === 0 ?
undefined : // We sort so that when applied in order the fixes will start from the end of the line to avoid conflicts
result.sort((a, b) => a.range[0] == b.range[0] ? b.range[1] - a.range[1] : b.range[0] - a.range[0]);
}
exports.NAMING_CONVENTION = {
createSearch: (_config) => flowr_search_builder_1.Q.all().filter(vertex_1.VertexType.VariableDefinition),
processSearchResult: (elements, config, data) => {
const symbols = elements.getElements()
.map(m => ({
certainty: linter_format_1.LintingResultCertainty.Certain,
detectedCasing: detectCasing(m.node.lexeme),
name: m.node.lexeme,
range: m.node.info.fullRange,
id: m.node.info.id
}));
const casing = config.caseing === 'auto' ? getMostUsedCasing(symbols) : config.caseing;
const results = symbols.filter(m => m.detectedCasing !== casing)
.map(({ id, ...m }) => ({
...m,
quickFix: createNamingConventionQuickFixes(data.dataflow.graph, id, fixCasing(m.name, casing), casing)
}));
return {
results: results,
'.meta': {
numMatches: symbols.length - results.length,
numBreak: results.length
}
};
},
prettyPrint: {
[linter_format_1.LintingPrettyPrintContext.Query]: result => `Identifier '${result.name}' at ${(0, dfg_1.formatRange)(result.range)} (${result.detectedCasing})`,
[linter_format_1.LintingPrettyPrintContext.Full]: result => `Identifier '${result.name}' at ${(0, dfg_1.formatRange)(result.range)} follows wrong convention: ${result.detectedCasing}`
},
info: {
name: 'Naming Convention',
// detects casing heuristically so correctness is not ensured using default config, but checks all identifiers in the code for naming convention match
certainty: linter_format_1.LintingRuleCertainty.OverApproximative,
description: 'Checks wether the symbols conform to a certain naming convention',
tags: [linter_tags_1.LintingRuleTag.Style, linter_tags_1.LintingRuleTag.QuickFix],
defaultConfig: {
caseing: 'auto'
}
}
};
//# sourceMappingURL=naming-convention.js.map