UNPKG

@eagleoutice/flowr

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

170 lines 7.48 kB
"use strict"; 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