diff options
29 files changed, 1998 insertions, 0 deletions
@@ -45,6 +45,7 @@ asterisk.* | [`datetimestamps`](plugins/datetimestamps.lua?raw=1) | Insert date-, time- and date-time-stamps | | [`discord-presence`](https://github.com/vincens2005/lite-xl-discord)\* | Adds the current workspace and file to your Discord Rich Presence | | [`dragdropselected`](plugins/dragdropselected.lua?raw=1) | Provides basic drag and drop of selected text (in same document) | +| [`editorconfig`](plugins/editorconfig) | [EditorConfig](https://editorconfig.org/) implementation for Lite XL | | [`eofnewline`](https://github.com/bokunodev/lite_modules/blob/master/plugins/eofnewline-xl.lua?raw=1) | Make sure the file ends with one blank line. | | [`ephemeral_tabs`](plugins/ephemeral_tabs.lua?raw=1) | Preview tabs. Opening a doc will replace the contents of the preview tab. Marks tabs as non-preview on any change or tab double clicking. | | [`equationgrapher`](https://github.com/ThaCuber/equationgrapher?raw=1)\* | Graphs y=x equations. | diff --git a/plugins/editorconfig/README.md b/plugins/editorconfig/README.md new file mode 100644 index 0000000..c3abd51 --- /dev/null +++ b/plugins/editorconfig/README.md @@ -0,0 +1,61 @@ +# EditorConfig + +This plugin implements the [EditorConfig](https://editorconfig.org/) spec +purely on lua by leveraging lua patterns and the regex engine on lite-xl. +Installing additional dependencies is not required. + +The EditorConfig spec was implemented as best understood, +if you find any bugs please report them on this repository +[issue tracker](https://github.com/lite-xl/lite-xl-plugins/issues). + +## Implemented Features + +Global options: + +* root - prevents upward searching of .editorconfig files + +Applied to documents indent info: + +* indent_style +* indent_size +* tab_width + +Applied on document save: + +* end_of_line - if set to `cr` it is ignored +* trim_trailing_whitespace +* insert_final_newline boolean + +## Not implemented + +* charset - this feature would need the encoding + [PR](https://github.com/lite-xl/lite-xl/pull/1161) or + [plugin](https://github.com/jgmdev/lite-xl-encoding) + +## Extras + +* Supports multiple project directories +* Implements hot reloading, so modifying an .editorconfig file from within + the editor will re-apply all rules to currently opened files. + +## Testing + +This plugin includes a test suite to check how well the .editorconfig parser +is working. + +The [editorconfig-core-test](https://github.com/editorconfig/editorconfig-core-test) +glob, parser and properties cmake tests where ported and we are getting a 100% +pass rate. + +If you are interested in running the test suite, from the terminal execute +the following: + +```sh +lite-xl test editorconfig +``` + +To inspect the generated sections and regex rules: + +```sh +lite-xl test editorconfig --parsers +``` diff --git a/plugins/editorconfig/init.lua b/plugins/editorconfig/init.lua new file mode 100644 index 0000000..a3df02c --- /dev/null +++ b/plugins/editorconfig/init.lua @@ -0,0 +1,441 @@ +-- mod-version:3 +-- +-- EditorConfig plugin for Lite XL +-- @copyright Jefferson Gonzalez <jgmdev@gmail.com> +-- @license MIT +-- +-- Note: this plugin needs to be loaded after detectindent plugin, +-- since the name editorconfig.lua is ordered after detectindent.lua +-- there shouldn't be any issues. Just a reminder for the future in +-- case of a plugin that could also handle document identation type +-- and size, and has a name with more weight than this plugin. +-- +local core = require "core" +local common = require "core.common" +local config = require "core.config" +local trimwhitespace = require "plugins.trimwhitespace" +local Doc = require "core.doc" +local Parser = require "plugins.editorconfig.parser" + +---@class config.plugins.editorconfig +---@field debug boolean +config.plugins.editorconfig = common.merge({ + debug = false, + -- The config specification used by the settings gui + config_spec = { + name = "EditorConfig", + { + label = "Debug", + description = "Display debugging messages on the log.", + path = "debug", + type = "toggle", + default = false + } + } +}, config.plugins.editorconfig) + +---Cache of .editorconfig options to reduce parsing for every opened file. +---@type table<string, plugins.editorconfig.parser> +local project_configs = {} + +---Keep track of main project directory so when changed we can assign a new +---.editorconfig object if neccesary. +---@type string +local main_project = core.project_dir + +---Functionality that will be exposed by the plugin. +---@class plugins.editorconfig +local editorconfig = {} + +---Load global .editorconfig options for a project. +---@param project_dir string +---@return boolean loaded +function editorconfig.load(project_dir) + local editor_config = project_dir .. "/" .. ".editorconfig" + local file = io.open(editor_config) + if file then + file:close() + project_configs[project_dir] = Parser.new(editor_config) + return true + end + return false +end + +---Helper to add or substract final new line, it also makes final new line +---visble which lite-xl does not. +---@param doc core.doc +---@param raw? boolean If true does not register change on undo stack +---@return boolean handled_new_line +local function handle_final_new_line(doc, raw) + local handled = false + ---@diagnostic disable-next-line + if doc.insert_final_newline then + handled = true + if doc.lines[#doc.lines] ~= "\n" then + if not raw then + doc:insert(#doc.lines, math.huge, "\n") + else + table.insert(doc.lines, "\n") + end + end + ---@diagnostic disable-next-line + elseif type(doc.insert_final_newline) == "boolean" then + handled = true + if trimwhitespace.trim_empty_end_lines then + trimwhitespace.trim_empty_end_lines(doc, raw) + -- TODO: remove this once 2.1.1 is released + else + for _=#doc.lines, 1, -1 do + local l = #doc.lines + if l > 1 and doc.lines[l] == "\n" then + local current_line = doc:get_selection() + if current_line == l then + doc:set_selection(l-1, math.huge, l-1, math.huge) + end + if not raw then + doc:remove(l-1, math.huge, l, math.huge) + else + table.remove(doc.lines, l) + end + end + end + end + end + return handled +end + +---Split the given relative path by / or \ separators. +---@param path string The path to split +---@return table +local function split_path(path) + local result = {}; + for match in (path.."/"):gmatch("(.-)".."[\\/]") do + table.insert(result, match); + end + return result; +end + +---Check if the given file path exists. +---@param file_path string +local function file_exists(file_path) + local file = io.open(file_path, "r") + if not file then return false end + file:close() + return true +end + +---Merge a config options to target if they don't already exists on target. +---@param config_target? plugins.editorconfig.parser.section +---@param config_from? plugins.editorconfig.parser.section +local function merge_config(config_target, config_from) + if config_target and config_from then + for name, value in pairs(config_from) do + if type(config_target[name]) == "nil" then + config_target[name] = value + end + end + end +end + +---Scan for .editorconfig files from current file path to upper project path +---if root attribute is not found first and returns matching config. +---@param file_path string +---@return plugins.editorconfig.parser.section? +local function recursive_get_config(file_path) + local project_dir = "" + + local root_config + for path, editor_config in pairs(project_configs) do + if common.path_belongs_to(file_path, path) then + project_dir = path + root_config = editor_config:getConfig( + common.relative_path(path, file_path) + ) + break + end + end + + if project_dir == "" then + for _, project in ipairs(core.project_directories) do + if common.path_belongs_to(file_path, project.name) then + project_dir = project.name + break + end + end + end + + local relative_file_path = common.relative_path(project_dir, file_path) + local dir = common.dirname(relative_file_path) + + local editor_config = {} + local config_found = false + if not dir and root_config then + editor_config = root_config + config_found = true + elseif dir then + local path_list = split_path(dir) + local root_found = false + for p=#path_list, 1, -1 do + local path = project_dir .. "/" .. table.concat(path_list, "/", 1, p) + if file_exists(path .. "/" .. ".editorconfig") then + ---@type plugins.editorconfig.parser + local parser = Parser.new(path .. "/" .. ".editorconfig") + local pconfig = parser:getConfig(common.relative_path(path, file_path)) + if pconfig then + merge_config(editor_config, pconfig) + config_found = true + end + if parser.root then + root_found = true + break + end + end + end + if not root_found and root_config then + merge_config(editor_config, root_config) + config_found = true + end + end + + -- clean unset options + if config_found then + local all_unset = true + for name, value in pairs(editor_config) do + if value == "unset" then + editor_config[name] = nil + else + all_unset = false + end + end + if all_unset then config_found = false end + end + + return config_found and editor_config or nil +end + +---Apply editorconfig rules to given doc if possible. +---@param doc core.doc +function editorconfig.apply(doc) + if not doc.abs_filename and not doc.filename then return end + local file_path = doc.abs_filename or (main_project .. "/" .. doc.filename) + local options = recursive_get_config(file_path) + if options then + if config.plugins.editorconfig.debug then + core.log_quiet( + "[EditorConfig]: %s applied %s", + file_path, common.serialize(options, {pretty = true}) + ) + end + local indent_type, indent_size = doc:get_indent_info() + if options.indent_style then + if options.indent_style == "tab" then + indent_type = "hard" + else + indent_type = "soft" + end + end + + if options.indent_size and options.indent_size == "tab" then + if options.tab_width then + options.indent_size = options.tab_width + else + options.indent_size = config.indent_size or 2 + end + end + + if options.indent_size then + indent_size = options.indent_size + end + + if doc.indent_info then + doc.indent_info.type = indent_type + doc.indent_info.size = indent_size + doc.indent_info.confirmed = true + else + doc.indent_info = { + type = indent_type, + size = indent_size, + confirmed = true + } + end + + if options.end_of_line then + if options.end_of_line == "crlf" then + doc.crlf = true + elseif options.end_of_line == "lf" then + doc.crlf = false + end + end + + if options.trim_trailing_whitespace then + doc.trim_trailing_whitespace = true + elseif options.trim_trailing_whitespace == false then + doc.trim_trailing_whitespace = false + else + doc.trim_trailing_whitespace = nil + end + + if options.insert_final_newline then + doc.insert_final_newline = true + elseif options.insert_final_newline == false then + doc.insert_final_newline = false + else + doc.insert_final_newline = nil + end + + if + ( + type(doc.trim_trailing_whitespace) == "boolean" + or + type(doc.insert_final_newline) == "boolean" + ) + -- TODO: remove this once 2.1.1 is released + and + trimwhitespace.disable + then + trimwhitespace.disable(doc) + end + + handle_final_new_line(doc, true) + end +end + +---Applies .editorconfig options to all open documents if possible. +function editorconfig.apply_all() + for _, doc in ipairs(core.docs) do + editorconfig.apply(doc) + end +end + +-------------------------------------------------------------------------------- +-- Load .editorconfig on all projects loaded at startup and apply it +-------------------------------------------------------------------------------- +core.add_thread(function() + local loaded = false + + -- scan all opened project directories + if core.project_directories then + for i=1, #core.project_directories do + local found = editorconfig.load(core.project_directories[i].name) + if found then loaded = true end + end + end + + -- if an editorconfig was found then try to apply it to opened docs + if loaded then + editorconfig.apply_all() + end +end) + +-------------------------------------------------------------------------------- +-- Override various core project loading functions for .editorconfig scanning +-------------------------------------------------------------------------------- +local core_open_folder_project = core.open_folder_project +function core.open_folder_project(directory) + core_open_folder_project(directory) + if project_configs[main_project] then project_configs[main_project] = nil end + main_project = core.project_dir + editorconfig.load(main_project) +end + +local core_remove_project_directory = core.remove_project_directory +function core.remove_project_directory(path) + local out = core_remove_project_directory(path) + if project_configs[path] then project_configs[path] = nil end + return out +end + +local core_add_project_directory = core.add_project_directory +function core.add_project_directory(directory) + local out = core_add_project_directory(directory) + editorconfig.load(directory) + return out +end + +-------------------------------------------------------------------------------- +-- Hook into the core.doc to apply editor config options +-------------------------------------------------------------------------------- +local doc_new = Doc.new +function Doc:new(...) + doc_new(self, ...) + editorconfig.apply(self) +end + +---Cloned trimwitespace plugin until it is exposed for other plugins. +---@param doc core.doc +local function trim_trailing_whitespace(doc) + if trimwhitespace.trim then + trimwhitespace.trim(doc) + return + end + + -- TODO: remove this once 2.1.1 is released + local cline, ccol = doc:get_selection() + for i = 1, #doc.lines do + local old_text = doc:get_text(i, 1, i, math.huge) + local new_text = old_text:gsub("%s*$", "") + + -- don't remove whitespace which would cause the caret to reposition + if cline == i and ccol > #new_text then + new_text = old_text:sub(1, ccol - 1) + end + + if old_text ~= new_text then + doc:insert(i, 1, new_text) + doc:remove(i, #new_text + 1, i, math.huge) + end + end +end + +local doc_save = Doc.save +function Doc:save(...) + local new_file = self.new_file + + ---@diagnostic disable-next-line + if self.trim_trailing_whitespace then + trim_trailing_whitespace(self) + end + + local lc = #self.lines + local handle_new_line = handle_final_new_line(self) + + -- remove the unnecesary visible \n\n or the disabled \n + if handle_new_line then + self.lines[lc] = self.lines[lc]:gsub("\n$", "") + end + + doc_save(self, ...) + + -- restore the visible \n\n or disabled \n + if handle_new_line then + self.lines[lc] = self.lines[lc] .. "\n" + end + + if common.basename(self.abs_filename) == ".editorconfig" then + -- blindlessly reload related project .editorconfig options + for _, project in ipairs(core.project_directories) do + if common.path_belongs_to(self.abs_filename, project.name) then + editorconfig.load(project.name) + break + end + end + -- re-apply editorconfig options to all open files + editorconfig.apply_all() + elseif new_file then + -- apply editorconfig options for file that was previously unsaved + editorconfig.apply(self) + end +end + +-------------------------------------------------------------------------------- +-- Run the test suite if requested on CLI with: lite-xl test editorconfig +-------------------------------------------------------------------------------- +for i, argument in ipairs(ARGS) do + if argument == "test" and ARGS[i+1] == "editorconfig" then + require "plugins.editorconfig.runtest" + os.exit() + end +end + + +return editorconfig diff --git a/plugins/editorconfig/parser.lua b/plugins/editorconfig/parser.lua new file mode 100644 index 0000000..b0ec689 --- /dev/null +++ b/plugins/editorconfig/parser.lua @@ -0,0 +1,553 @@ +-- Lua parser implementation of the .editorconfig spec as best understood. +-- @copyright Jefferson Gonzalez <jgmdev@gmail.com> +-- @license MIT + +local core = require "core" +local config = require "core.config" + +local STANDALONE = false +for i, argument in ipairs(ARGS) do + if argument == "test" and ARGS[i+1] == "editorconfig" then + STANDALONE = true + end +end + +---Logger that will output using lite-xl logging functions or print to +---terminal if the parser is running in standalone mode. +---@param type "log" | "error" +---@param format string +---@param ... any +local function log(type, format, ...) + if not STANDALONE then + core[type]("[EditorConfig]: " .. format, ...) + else + print("[" .. type:upper() .. "]: " .. string.format(format, ...)) + end +end + +---Represents an .editorconfig path rule/expression. +---@class plugins.editorconfig.parser.rule +---Path expression as found between square brackets. +---@field expression string | table<integer,string> +---The expression converted to a regex. +---@field regex string | table<integer,string> +---@field regex_compiled any? | table<integer,string> +---@field negation boolean Indicates that the expression is a negation. +---@field ranges table<integer,number> List of ranges found on the expression. + +---Represents a section of the .editorconfig with all its config options. +---@class plugins.editorconfig.parser.section +---@field rule plugins.editorconfig.parser.rule +---@field equivalent_rules plugins.editorconfig.parser.rule[] +---@field indent_style "tab" | "space" +---@field indent_size integer +---@field tab_width integer +---@field end_of_line "lf" | "cr" | "crlf" +---@field charset "latin1" | "utf-8" | "utf-8-bom" | "utf-16be" | "utf-16le" +---@field trim_trailing_whitespace boolean +---@field insert_final_newline boolean + +---EditorConfig parser class and filename config matching. +---@class plugins.editorconfig.parser +---@field config_path string +---@field sections plugins.editorconfig.parser.section[] +---@field root boolean +local Parser = {} +Parser.__index = Parser + +---Constructor +---@param config_path string +---@return plugins.editorconfig.parser +function Parser.new(config_path) + local self = {} + setmetatable(self, Parser) + self.config_path = config_path + self.sections = {} + self.root = false + self:read() + return self +end + +--- char to hex cache and automatic converter +---@type table<string,string> +local hex_value = {} +setmetatable(hex_value, { + __index = function(t, k) + local v = rawget(t, k) + if v == nil then + v = string.format("%x", string.byte(k)) + rawset(t, k, v) + end + return v + end +}) + +---Simplifies managing rules with other inner rules like {...} which can +---contain escaped \\{ \\} and expressions that are easier handled after +---converting the escaped special characters to \xXX counterparts. +---@param value string +---@return string escaped_values +local function escapes_to_regex_hex(value) + local escaped_chars = {} + for char in value:ugmatch("\\(.)") do + table.insert(escaped_chars, char) + end + for _, char in ipairs(escaped_chars) do + value = value:ugsub("\\" .. char, "\\x" .. hex_value[char]) + end + return value +end + +---An .editorconfig path expression to regex conversion rule. +---@class rule +---@field rule string Lua pattern. +---Callback conversion function. +---@field conversion fun(match:string, section:plugins.editorconfig.parser.section):string + +---List of conversion rules applied to brace expressions. +---@type rule[] +local RULES_BRACES = { + { rule = "^%(", conversion = function() return "\\(" end }, + { rule = "^%)", conversion = function() return "\\)" end }, + { rule = "^%.", conversion = function() return "\\." end }, + { rule = "^\\%[", conversion = function() return "\\[" end }, + { rule = "^\\%]", conversion = function() return "\\]" end }, + { rule = "^\\!", conversion = function() return "!" end }, + { rule = "^\\;", conversion = function() return ";" end }, + { rule = "^\\#", conversion = function() return "#" end }, + { rule = "^\\,", conversion = function() return "," end }, + { rule = "^\\{", conversion = function() return "{" end }, + { rule = "^\\}", conversion = function() return "}" end }, + { rule = "^,", conversion = function() return "|" end }, + { rule = "^\\%*", conversion = function() return "\\*" end }, + { rule = "^%*", conversion = function() return "[^\\/]*" end }, + { rule = "^%*%*", conversion = function() return ".*" end }, + { rule = "^%?", conversion = function() return "." end }, + { rule = "^{}", conversion = function() return "{}" end }, + { rule = "^{[^,]+}", conversion = function(match) return match end }, + { rule = "^%b{}", + conversion = function(match) + local out = match:ugsub("%(", "\\(") + :ugsub("%)", "\\)") + :ugsub("%.", "\\.") + :ugsub("\\%[", "[\\[]") + :ugsub("\\%]", "[\\]]") + :ugsub("^\\!", "!") + :ugsub("^\\;", ";") + :ugsub("^\\#", "#") + -- negation chars list + :ugsub("%[!(%a+)%]", "[^%1]") + :ugsub("\\\\", "[\\]") + -- escaped braces + :ugsub("\\{", "[{]") + :ugsub("\\}", "[}]") + -- non escaped braces + :ugsub("{([^%]])", "(%1") + :ugsub("}([^%]])", ")%1") + :ugsub("^{", "(") + :ugsub("}$", ")") + -- escaped globs + :ugsub("\\%*", "[\\*]") + :ugsub("\\%?", "[\\?]") + -- non escaped globs + :ugsub("%*%*", "[*][*]") -- prevent this glob from expanding to next sub + :ugsub("%*([^%]])", "[^\\/]*%1") + :ugsub("%[%*%]%[%*%]", ".*") + :ugsub("%?([^%]])", ".%1") + -- escaped comma + :ugsub("\\,", "[,]") + -- non escaped comma + :ugsub(",([^%]])", "|%1") + return out + end + }, + { rule = "^%[[^/%]]*%]", + conversion = function(match) + local negation = match:umatch("^%[!") + local chars = match:umatch("^%[!?(.-)%]") + chars = chars:ugsub("^%-", "\\-"):ugsub("%-$", "\\-") + local out = "" + if negation then + out = "[^"..chars.."]" + else + out = "["..chars.."]" + end + return out + end + }, +} + +---List of conversion rules applied to .editorconfig path expressions. +---@type rule[] +local RULES = { + -- normalize escaped .editorconfig special chars or keep them escaped + { rule = "^\\x[a-fA-F][a-fA-F]", conversion = function(match) return match end }, + { rule = "^\\%*", conversion = function() return "\\*" end }, + { rule = "^\\%?", conversion = function() return "\\?" end }, + { rule = "^\\{", conversion = function() return "{" end }, + { rule = "^\\}", conversion = function() return "}" end }, + { rule = "^\\%[", conversion = function() return "\\[" end }, + { rule = "^\\%]", conversion = function() return "\\]" end }, + { rule = "^\\!", conversion = function() return "!" end }, + { rule = "^\\;", conversion = function() return ";" end }, + { rule = "^\\#", conversion = function() return "#" end }, + -- escape special chars + { rule = "^%.", conversion = function() return "\\." end }, + { rule = "^%(", conversion = function() return "\\(" end }, + { rule = "^%)", conversion = function() return "\\)" end }, + { rule = "^%[[^/%]]*%]", + conversion = function(match) + local negation = match:umatch("^%[!") + local chars = match:umatch("^%[!?(.-)%]") + chars = chars:ugsub("^%-", "\\-"):ugsub("%-$", "\\-") + local out = "" + if negation then + out = "[^"..chars.."]" + else + out = "["..chars.."]" + end + return out + end + }, + -- Is this negation rule valid? + { rule = "^!%w+", + conversion = function(match) + local chars = match:umatch("%w+") + return "[^"..chars.."]" + end + }, + -- escape square brackets + { rule = "^%[", conversion = function() return "\\[" end }, + { rule = "^%]", conversion = function() return "\\]" end }, + -- match any characters + { rule = "^%*%*", conversion = function() return ".*" end }, + -- match any characters excluding path separators, \ not needed but just in case + { rule = "^%*", conversion = function() return "[^\\/]*" end }, + -- match optional character, doesn't matters what or should only be a \w? + { rule = "^%?", conversion = function() return "[^/]" end }, + -- threat empty braces literally + { rule = "^{}", conversion = function() return "{}" end }, + -- match a number range + { rule = "^{%-?%d+%.%.%-?%d+}", + conversion = function(match, section) + local min, max = match:umatch("(-?%d+)%.%.(-?%d+)") + min = tonumber(min) + max = tonumber(max) + if min and max then + if not section.rule.ranges then section.rule.ranges = {} end + table.insert(section.rule.ranges, { + math.min(min, max), + math.max(min, max) + }) + end + local minus = "" + if min < 0 or max < 0 then minus = "\\-?" end + return "(?<!0)("..minus.."[1-9]\\d*)" + end + }, + -- threat single option braces literally + { rule = "^{[^,]+}", conversion = function(match) return match end }, + -- match invalid range + { rule = "^{[^%.]+%.%.[^%.]+}", conversion = function(match) return match end }, + -- match any of the strings separated by commas inside the curly braces + { rule = "^%b{}", + conversion = function(rule, section) + rule = rule:gsub("^{", ""):gsub("}$", "") + local pos, len, exp = 1, rule:ulen(), "" + + while pos <= len do + local found = false + for _, r in ipairs(RULES_BRACES) do + local match = rule:umatch(r.rule, pos) + if match then + exp = exp .. r.conversion(match, section) + pos = pos + match:ulen() + found = true + break + end + end + if not found then + exp = exp .. rule:usub(pos, pos) + pos = pos + 1 + end + end + + return "(" .. exp .. ")" + end + } +} + +---Adds the regex equivalent of a section path expression. +---@param section plugins.editorconfig.parser.section | string +---@return plugins.editorconfig.parser.section +function Parser:rule_to_regex(section) + if type(section) == "string" then + section = {rule = {expression = section}} + end + + local rule = section.rule.expression + + -- match everything rule which is different from regular * + -- that doesn't matches path separators + if rule == "*" then + section.rule.regex = ".+" + section.rule.regex_compiled = regex.compile(".+") + return section + end + + rule = escapes_to_regex_hex(section.rule.expression) + + local pos, len, exp = 1, rule:ulen(), "" + + -- if expression starts with ! it is treated entirely as a negation + local negation = rule:umatch("^%s*!") + if negation then + pos = pos + negation:ulen() + 1 + end + + -- apply all conversion rules by looping the path expression/rule + while pos <= len do + local found = false + for _, r in ipairs(RULES) do + local match = rule:umatch(r.rule, pos) + if match then + exp = exp .. r.conversion(match, section) + pos = pos + match:ulen() + found = true + break + end + end + if not found then + exp = exp .. rule:usub(pos, pos) + pos = pos + 1 + end + end + + -- force match up to the end + exp = exp .. "$" + + -- allow expressions that start with * to match anything on start + if exp:match("^%[^\\/%]%*") then + exp = exp:gsub("^%[^\\/%]%*", ".*") + -- fixes two failing tests + elseif exp:match("^%[") then + exp = "^" .. exp + -- match only on root dir + elseif exp:match("^/") then + exp = exp:gsub("^/", "^") + end + + -- store changes to the section rule + section.rule.regex, section.rule.negation = exp, negation + section.rule.regex_compiled = regex.compile(section.rule.regex) + if not section.rule.regex_compiled then + log( + "error", + "could not compile '[%s]' to regex '%s'", + rule, section.rule.regex + ) + end + + return section +end + +---Parses the associated .editorconfig file and stores each section. +function Parser:read() + local file = io.open(self.config_path, "r") + + self.sections = {} + + if not file then + log("log", "could not read %s", self.config_path) + return + end + + ---@type plugins.editorconfig.parser.section + local section = {} + + for line in file:lines() do + ---@cast line string + + -- first we try to see if the line is a rule section + local rule = "" + rule = line:umatch("^%s*%[(.+)%]%s*$") + if rule then + if section.rule then + -- save previous section and crerate new one + table.insert(self.sections, section) + section = {} + end + section.rule = { + expression = rule + } + -- convert the expression to a regex directly on the section table + self:rule_to_regex(section) + + local clone = rule + if clone:match("//+") or clone:match("/%*%*/") then + section.equivalent_rules = {} + end + while clone:match("//+") or clone:match("/%*%*/") do + ---@type plugins.editorconfig.parser.section[] + if clone:match("//+") then + clone = clone:ugsub("//+", "/", 1) + table.insert(section.equivalent_rules, self:rule_to_regex(clone).rule) + end + if clone:match("/%*%*/") then + clone = clone:ugsub("/%*%*/", "/", 1) + table.insert(section.equivalent_rules, self:rule_to_regex(clone).rule) + end + end + end + + if not rule then + local name, value = line:umatch("^%s*(%w%S+)%s*=%s*([^\n\r]+)") + if name and value then + name = name:ulower() + -- do not lowercase property values that start with test_ + if not name:match("^test_") then + value = value:ulower() + end + if value == "true" then + value = true + elseif value == "false" then + value = false + elseif math.tointeger and math.tointeger(value) then + value = math.tointeger(value) + elseif tonumber(value) then + value = tonumber(value) + end + + if section.rule then + section[name] = value + elseif name == "root" and type(value) == "boolean" then + self.root = value + end + end + end + end + + if section.rule then + table.insert(self.sections, section) + end +end + +---Helper function that converts a regex offset results into a list +---of strings, omitting the first result which is the complete match. +---@param offsets table<integer,integer> +---@param value string +---@return table<integer, string> +local function regex_result_to_table(offsets, value) + local result = {} + local offset_fix = 0 + if not regex.find_offsets then + offset_fix = 1 + end + for i=3, #offsets, 2 do + table.insert(result, value:sub(offsets[i], offsets[i+1]-offset_fix)) + end + return result +end + +---Get a matching config for the given filename or nil if nothing found. +---@param file_name string +---@param defaults? boolean Set indent size to defaults when needed, +---@return plugins.editorconfig.parser.section? +function Parser:getConfig(file_name, defaults) + if PLATFORM == "Windows" then + file_name = file_name:gsub("\\", "/") + end + + local regex_match = regex.match + if regex.find_offsets then + regex_match = regex.find_offsets + end + + local properties = {} + + local found = false + for _, section in ipairs(self.sections) do + if section.rule.regex_compiled then + local negation = section.rule.negation + -- default rule + local matched = {regex_match(section.rule.regex_compiled, file_name)} + -- try equivalent rules if available + if not matched[1] and section.equivalent_rules then + for _, esection in ipairs(section.equivalent_rules) do + matched = {regex_match(esection.regex_compiled, file_name)} + if matched[1] then + break + end + end + end + if (matched[1] and not negation) or (not matched[1] and negation) then + local ranges_match = true + if section.rule.ranges then + local results = regex_result_to_table(matched, file_name) + if #results < #section.rule.ranges then + ranges_match = false + else + for i, range in ipairs(section.rule.ranges) do + local number = tonumber(results[i]) + if not number then + ranges_match = false + break + end + if number < range[1] or number > range[2] then + ranges_match = false + break + end + end + end + end + if ranges_match then + found = true + for name, value in pairs(section) do + if name ~= "rule" and name ~= "equivalent_rules" then + properties[name] = value + end + end + end + end + end + end + + if found and defaults then + if properties.indent_style and properties.indent_style == "space" then + if properties.indent_size and not properties.tab_width then + properties.tab_width = 4 + end + elseif properties.indent_style and properties.indent_style == "tab" then + if not properties.tab_width and not properties.indent_size then + properties.indent_size = "tab" + elseif properties.tab_width then + properties.indent_size = properties.tab_width + end + end + end + + return found and properties or nil +end + +---Get a matching config for the given filename or nil if nothing found. +---@param file_name string +---@return string +function Parser:getConfigString(file_name) + local out = "" + local properties = self:getConfig(file_name, true) + if properties then + local config_sorted = {} + for name, value in pairs(properties) do + table.insert(config_sorted, {name = name, value = value}) + end + table.sort(config_sorted, function(a, b) + return a.name < b.name + end) + for _, value in ipairs(config_sorted) do + out = out .. value.name .. "=" .. tostring(value.value) .. "\n" + end + end + return out +end + +return Parser diff --git a/plugins/editorconfig/runtest.lua b/plugins/editorconfig/runtest.lua new file mode 100644 index 0000000..85378cd --- /dev/null +++ b/plugins/editorconfig/runtest.lua @@ -0,0 +1,63 @@ +local core = require "core" +local tests = require "plugins.editorconfig.tests" + +-- disable print buffer for immediate output +io.stdout:setvbuf "no" + +-- overwrite to print into stdout +function core.error(format, ...) + print(string.format(format, ...)) +end + +function core.log(format, ...) + print(string.format(format, ...)) +end + +function core.log_quiet(format, ...) + print(string.format(format, ...)) +end + +-- check if --parsers flag was given to only output the path expressions and +-- their conversion into regular expressions. +local PARSERS = false +for _, argument in ipairs(ARGS) do + if argument == "--parsers" then + PARSERS = true + end +end + +if not PARSERS then + require "plugins.editorconfig.tests.glob" + require "plugins.editorconfig.tests.parser" + require "plugins.editorconfig.tests.properties" + + tests.run() +else + -- Globs + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/braces.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/brackets.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/question.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/star.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/star_star.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/glob/utf8char.in") + + -- Parser + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/basic.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/bom.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/comments.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/comments_and_newlines.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/comments_only.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/crlf.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/empty.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/limits.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/newlines_only.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/parser/whitespace.in") + + -- Properties + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/indent_size_default.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/lowercase_names.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/lowercase_values.in") + tests.add_parser(USERDIR .. "/plugins/editorconfig/tests/properties/tab_width_default.in") + + tests.run_parsers() +end diff --git a/plugins/editorconfig/tests/glob/braces.in b/plugins/editorconfig/tests/glob/braces.in new file mode 100644 index 0000000..0400aeb --- /dev/null +++ b/plugins/editorconfig/tests/glob/braces.in @@ -0,0 +1,71 @@ +; test { and } + +root=true + +; word choice +[*.{py,js,html}] +choice=true + +; single choice +[{single}.b] +choice=single + +; empty choice +[{}.c] +empty=all + +; choice with empty word +[a{b,c,}.d] +empty=word + +; choice with empty words +[a{,b,,c,}.e] +empty=words + +; no closing brace +[{.f] +closing=false + +; nested braces +[{word,{also},this}.g] +nested=true + +; nested braces, adjacent at start +[{{a,b},c}.k] +nested_start=true + +; nested braces, adjacent at end +[{a,{b,c}}.l] +nested_end=true + +; closing inside beginning +[{},b}.h] +closing=inside + +; opening inside beginning +[{{,b,c{d}.i] +unmatched=true + +; escaped comma +[{a\,b,cd}.txt] +comma=yes + +; escaped closing brace +[{e,\},f}.txt] +closing=yes + +; escaped backslash +[{g,\\,i}.txt] +backslash=yes + +; patterns nested in braces +[{some,a{*c,b}[ef]}.j] +patterns=nested + +; numeric braces +[{3..120}] +number=true + +; alphabetical +[{aardvark..antelope}] +words=a diff --git a/plugins/editorconfig/tests/glob/brackets.in b/plugins/editorconfig/tests/glob/brackets.in new file mode 100644 index 0000000..f44def2 --- /dev/null +++ b/plugins/editorconfig/tests/glob/brackets.in @@ -0,0 +1,51 @@ +; test [ and ] + +root=true + +; Character choice +[[ab].a] +choice=true + +; Negative character choice +[[!ab].b] +choice=false + +; Character range +[[d-g].c] +range=true + +; Negative character range +[[!d-g].d] +range=false + +; Range and choice +[[abd-g].e] +range_and_choice=true + +; Choice with dash +[[-ab].f] +choice_with_dash=true + +; Close bracket inside +[[\]ab].g] +close_inside=true + +; Close bracket outside +[[ab]].g] +close_outside=true + +; Negative close bracket inside +[[!\]ab].g] +close_inside=false + +; Negative¬close bracket outside +[[!ab]].g] +close_outside=false + +; Slash inside brackets +[ab[e/]cd.i] +slash_inside=true + +; Slash after an half-open bracket +[ab[/c] +slash_half_open=true diff --git a/plugins/editorconfig/tests/glob/init.lua b/plugins/editorconfig/tests/glob/init.lua new file mode 100644 index 0000000..f1214c3 --- /dev/null +++ b/plugins/editorconfig/tests/glob/init.lua @@ -0,0 +1,241 @@ +local tests = require "plugins.editorconfig.tests" + +-- Tests for * + +-- matches a single characters +tests.add("star_single_ML", "glob/star.in", "ace.c", "key=value[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- matches zero characters +tests.add("star_zero_ML", "glob/star.in", "ae.c", "key=value[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- matches multiple characters +tests.add("star_multiple_ML", "glob/star.in", "abcde.c", "key=value[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- does not match path separator +tests.add("star_over_slash", "glob/star.in", "a/e.c", "^[ \t\n\r]*keyc=valuec[ \t\n\r]*$") + +-- star after a slash +tests.add("star_after_slash_ML", "glob/star.in", "Bar/foo.txt", "keyb=valueb[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- star matches a dot file after slash +tests.add("star_matches_dot_file_after_slash_ML", "glob/star.in", "Bar/.editorconfig", "keyb=valueb[ \t\n\r]+keyc=valuec[ \t\n\r]*") + +-- star matches a dot file +tests.add("star_matches_dot_file", "glob/star.in", ".editorconfig", "^keyc=valuec[ \t\n\r]*$") + +-- Tests for ? + +-- matches a single character +tests.add("question_single", "glob/question.in", "some.c", "^key=value[ \t\n\r]*$") + +-- does not match zero characters +tests.add("question_zero", "glob/question.in", "som.c", "^[ \t\n\r]*$") + +-- does not match multiple characters +tests.add("question_multiple", "glob/question.in", "something.c", "^[ \t\n\r]*$") + +-- does not match slash +tests.add("question_slash", "glob/question.in", "som/.c", "^[ \t\n\r]*$") + +-- Tests for [ and ] + +-- close bracket inside +tests.add("brackets_close_inside", "glob/brackets.in", "].g", "^close_inside=true[ \t\n\r]*$") + +-- close bracket outside +tests.add("brackets_close_outside", "glob/brackets.in", "b].g", "^close_outside=true[ \t\n\r]*$") + +-- negative close bracket inside +tests.add("brackets_nclose_inside", "glob/brackets.in", "c.g", "^close_inside=false[ \t\n\r]*$") + +-- negative close bracket outside +tests.add("brackets_nclose_outside", "glob/brackets.in", "c].g", "^close_outside=false[ \t\n\r]*$") + +-- character choice +tests.add("brackets_choice", "glob/brackets.in", "a.a", "^choice=true[ \t\n\r]*$") + +-- character choice 2 +tests.add("brackets_choice2", "glob/brackets.in", "c.a", "^[ \t\n\r]*$") + +-- negative character choice +tests.add("brackets_nchoice", "glob/brackets.in", "c.b", "^choice=false[ \t\n\r]*$") + +-- negative character choice 2 +tests.add("brackets_nchoice2", "glob/brackets.in", "a.b", "^[ \t\n\r]*$") + +-- character range +tests.add("brackets_range", "glob/brackets.in", "f.c", "^range=true[ \t\n\r]*$") + +-- character range 2 +tests.add("brackets_range2", "glob/brackets.in", "h.c", "^[ \t\n\r]*$") + +-- negative character range +tests.add("brackets_nrange", "glob/brackets.in", "h.d", "^range=false[ \t\n\r]*$") + +-- negative character range 2 +tests.add("brackets_nrange2", "glob/brackets.in", "f.d", "^[ \t\n\r]*$") + +-- range and choice +tests.add("brackets_range_and_choice", "glob/brackets.in", "e.e", + "^range_and_choice=true[ \t\n\r]*$") + +-- character choice with a dash +tests.add("brackets_choice_with_dash", "glob/brackets.in", "-.f", + "^choice_with_dash=true[ \t\n\r]*$") + +-- slash inside brackets +tests.add("brackets_slash_inside1", "glob/brackets.in", "ab/cd.i", + "^[ \t\n\r]*$") +tests.add("brackets_slash_inside2", "glob/brackets.in", "abecd.i", + "^[ \t\n\r]*$") +tests.add("brackets_slash_inside3", "glob/brackets.in", "ab[e/]cd.i", + "^slash_inside=true[ \t\n\r]*$") +tests.add("brackets_slash_inside4", "glob/brackets.in", "ab[/c", + "^slash_half_open=true[ \t\n\r]*$") + +-- Tests for { and } + +-- word choice +tests.add("braces_word_choice1", "glob/braces.in", "test.py", "^choice=true[ \t\n\r]*$") +tests.add("braces_word_choice2", "glob/braces.in", "test.js", "^choice=true[ \t\n\r]*$") +tests.add("braces_word_choice3", "glob/braces.in", "test.html", "^choice=true[ \t\n\r]*$") +tests.add("braces_word_choice4", "glob/braces.in", "test.pyc", "^[ \t\n\r]*$") + +-- single choice +tests.add("braces_single_choice", "glob/braces.in", "{single}.b", "^choice=single[ \t\n\r]*$") +tests.add("braces_single_choice_negative", "glob/braces.in", ".b", "^[ \t\n\r]*$") + +-- empty choice +tests.add("braces_empty_choice", "glob/braces.in", "{}.c", "^empty=all[ \t\n\r]*$") +tests.add("braces_empty_choice_negative", "glob/braces.in", ".c", "^[ \t\n\r]*$") + +-- choice with empty word +tests.add("braces_empty_word1", "glob/braces.in", "a.d", "^empty=word[ \t\n\r]*$") +tests.add("braces_empty_word2", "glob/braces.in", "ab.d", "^empty=word[ \t\n\r]*$") +tests.add("braces_empty_word3", "glob/braces.in", "ac.d", "^empty=word[ \t\n\r]*$") +tests.add("braces_empty_word4", "glob/braces.in", "a,.d", "^[ \t\n\r]*$") + +-- choice with empty words +tests.add("braces_empty_words1", "glob/braces.in", "a.e", "^empty=words[ \t\n\r]*$") +tests.add("braces_empty_words2", "glob/braces.in", "ab.e", "^empty=words[ \t\n\r]*$") +tests.add("braces_empty_words3", "glob/braces.in", "ac.e", "^empty=words[ \t\n\r]*$") +tests.add("braces_empty_words4", "glob/braces.in", "a,.e", "^[ \t\n\r]*$") + +-- no closing brace +tests.add("braces_no_closing", "glob/braces.in", "{.f", "^closing=false[ \t\n\r]*$") +tests.add("braces_no_closing_negative", "glob/braces.in", ".f", "^[ \t\n\r]*$") + +-- nested braces +tests.add("braces_nested1", "glob/braces.in", "word,this}.g", "^[ \t\n\r]*$") +tests.add("braces_nested2", "glob/braces.in", "{also,this}.g", "^[ \t\n\r]*$") +tests.add("braces_nested3", "glob/braces.in", "word.g", "^nested=true[ \t\n\r]*$") +tests.add("braces_nested4", "glob/braces.in", "{also}.g", "^nested=true[ \t\n\r]*$") +tests.add("braces_nested5", "glob/braces.in", "this.g", "^nested=true[ \t\n\r]*$") + +-- nested braces, adjacent at start +tests.add("braces_nested_start1", "glob/braces.in", "{{a,b},c}.k", "^[ \t\n\r]*$") +tests.add("braces_nested_start2", "glob/braces.in", "{a,b}.k", "^[ \t\n\r]*$") +tests.add("braces_nested_start3", "glob/braces.in", "a.k", "^nested_start=true[ \t\n\r]*$") +tests.add("braces_nested_start4", "glob/braces.in", "b.k", "^nested_start=true[ \t\n\r]*$") +tests.add("braces_nested_start5", "glob/braces.in", "c.k", "^nested_start=true[ \t\n\r]*$") + +-- nested braces, adjacent at end +tests.add("braces_nested_end1", "glob/braces.in", "{a,{b,c}}.l", "^[ \t\n\r]*$") +tests.add("braces_nested_end2", "glob/braces.in", "{b,c}.l", "^[ \t\n\r]*$") +tests.add("braces_nested_end3", "glob/braces.in", "a.l", "^nested_end=true[ \t\n\r]*$") +tests.add("braces_nested_end4", "glob/braces.in", "b.l", "^nested_end=true[ \t\n\r]*$") +tests.add("braces_nested_end5", "glob/braces.in", "c.l", "^nested_end=true[ \t\n\r]*$") + +-- closing inside beginning +tests.add("braces_closing_in_beginning", "glob/braces.in", "{},b}.h", "^closing=inside[ \t\n\r]*$") + +-- missing closing braces +tests.add("braces_unmatched1", "glob/braces.in", "{{,b,c{d}.i", "^unmatched=true[ \t\n\r]*$") +tests.add("braces_unmatched2", "glob/braces.in", "{.i", "^[ \t\n\r]*$") +tests.add("braces_unmatched3", "glob/braces.in", "b.i", "^[ \t\n\r]*$") +tests.add("braces_unmatched4", "glob/braces.in", "c{d.i", "^[ \t\n\r]*$") +tests.add("braces_unmatched5", "glob/braces.in", ".i", "^[ \t\n\r]*$") + +-- escaped comma +tests.add("braces_escaped_comma1", "glob/braces.in", "a,b.txt", "^comma=yes[ \t\n\r]*$") +tests.add("braces_escaped_comma2", "glob/braces.in", "a.txt", "^[ \t\n\r]*$") +tests.add("braces_escaped_comma3", "glob/braces.in", "cd.txt", "^comma=yes[ \t\n\r]*$") + +-- escaped closing brace +tests.add("braces_escaped_brace1", "glob/braces.in", "e.txt", "^closing=yes[ \t\n\r]*$") +tests.add("braces_escaped_brace2", "glob/braces.in", "}.txt", "^closing=yes[ \t\n\r]*$") +tests.add("braces_escaped_brace3", "glob/braces.in", "f.txt", "^closing=yes[ \t\n\r]*$") + +-- escaped backslash +tests.add("braces_escaped_backslash1", "glob/braces.in", "g.txt", "^backslash=yes[ \t\n\r]*$") +if PLATFORM ~= "Windows" then +tests.add("braces_escaped_backslash2", "glob/braces.in", "\\.txt", "^backslash=yes[ \t\n\r]*$") +end +tests.add("braces_escaped_backslash3", "glob/braces.in", "i.txt", "^backslash=yes[ \t\n\r]*$") + +-- patterns nested in braces +tests.add("braces_patterns_nested1", "glob/braces.in", "some.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested2", "glob/braces.in", "abe.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested3", "glob/braces.in", "abf.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested4", "glob/braces.in", "abg.j", "^[ \t\n\r]*$") +tests.add("braces_patterns_nested5", "glob/braces.in", "ace.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested6", "glob/braces.in", "acf.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested7", "glob/braces.in", "acg.j", "^[ \t\n\r]*$") +tests.add("braces_patterns_nested8", "glob/braces.in", "abce.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested9", "glob/braces.in", "abcf.j", "^patterns=nested[ \t\n\r]*$") +tests.add("braces_patterns_nested10", "glob/braces.in", "abcg.j", "^[ \t\n\r]*$") +tests.add("braces_patterns_nested11", "glob/braces.in", "ae.j", "^[ \t\n\r]*$") +tests.add("braces_patterns_nested12", "glob/braces.in", ".j", "^[ \t\n\r]*$") + +-- numeric brace range +tests.add("braces_numeric_range1", "glob/braces.in", "1", "^[ \t\n\r]*$") +tests.add("braces_numeric_range2", "glob/braces.in", "3", "^number=true[ \t\n\r]*$") +tests.add("braces_numeric_range3", "glob/braces.in", "15", "^number=true[ \t\n\r]*$") +tests.add("braces_numeric_range4", "glob/braces.in", "60", "^number=true[ \t\n\r]*$") +tests.add("braces_numeric_range5", "glob/braces.in", "5a", "^[ \t\n\r]*$") +tests.add("braces_numeric_range6", "glob/braces.in", "120", "^number=true[ \t\n\r]*$") +tests.add("braces_numeric_range7", "glob/braces.in", "121", "^[ \t\n\r]*$") +tests.add("braces_numeric_range8", "glob/braces.in", "060", "^[ \t\n\r]*$") + +-- alphabetical brace range: letters should not be considered for ranges +tests.add("braces_alpha_range1", "glob/braces.in", "{aardvark..antelope}", "^words=a[ \t\n\r]*$") +tests.add("braces_alpha_range2", "glob/braces.in", "a", "^[ \t\n\r]*$") +tests.add("braces_alpha_range3", "glob/braces.in", "aardvark", "^[ \t\n\r]*$") +tests.add("braces_alpha_range4", "glob/braces.in", "agreement", "^[ \t\n\r]*$") +tests.add("braces_alpha_range5", "glob/braces.in", "antelope", "^[ \t\n\r]*$") +tests.add("braces_alpha_range6", "glob/braces.in", "antimatter", "^[ \t\n\r]*$") + + +-- Tests for ** + +-- test EditorConfig files with UTF-8 characters larger than 127 +tests.add("utf_8_char", "glob/utf8char.in", "中文.txt", "^key=value[ \t\n\r]*$") + +-- matches over path separator +tests.add("star_star_over_separator1", "glob/star_star.in", "a/z.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator2", "glob/star_star.in", "amnz.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator3", "glob/star_star.in", "am/nz.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator4", "glob/star_star.in", "a/mnz.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator5", "glob/star_star.in", "amn/z.c", "^key1=value1[ \t\n\r]*$") +tests.add("star_star_over_separator6", "glob/star_star.in", "a/mn/z.c", "^key1=value1[ \t\n\r]*$") + +tests.add("star_star_over_separator7", "glob/star_star.in", "b/z.c", "^key2=value2[ \t\n\r]*$") +tests.add("star_star_over_separator8", "glob/star_star.in", "b/mnz.c", "^key2=value2[ \t\n\r]*$") +tests.add("star_star_over_separator9", "glob/star_star.in", "b/mn/z.c", "^key2=value2[ \t\n\r]*$") +tests.add("star_star_over_separator10", "glob/star_star.in", "bmnz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator11", "glob/star_star.in", "bm/nz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator12", "glob/star_star.in", "bmn/z.c", "^[ \t\n\r]*$") + +tests.add("star_star_over_separator13", "glob/star_star.in", "c/z.c", "^key3=value3[ \t\n\r]*$") +tests.add("star_star_over_separator14", "glob/star_star.in", "cmn/z.c", "^key3=value3[ \t\n\r]*$") +tests.add("star_star_over_separator15", "glob/star_star.in", "c/mn/z.c", "^key3=value3[ \t\n\r]*$") +tests.add("star_star_over_separator16", "glob/star_star.in", "cmnz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator17", "glob/star_star.in", "cm/nz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator18", "glob/star_star.in", "c/mnz.c", "^[ \t\n\r]*$") + +tests.add("star_star_over_separator19", "glob/star_star.in", "d/z.c", "^key4=value4[ \t\n\r]*$") +tests.add("star_star_over_separator20", "glob/star_star.in", "d/mn/z.c", "^key4=value4[ \t\n\r]*$") +tests.add("star_star_over_separator21", "glob/star_star.in", "dmnz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator22", "glob/star_star.in", "dm/nz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator23", "glob/star_star.in", "d/mnz.c", "^[ \t\n\r]*$") +tests.add("star_star_over_separator24", "glob/star_star.in", "dmn/z.c", "^[ \t\n\r]*$") diff --git a/plugins/editorconfig/tests/glob/question.in b/plugins/editorconfig/tests/glob/question.in new file mode 100644 index 0000000..e2af52a --- /dev/null +++ b/plugins/editorconfig/tests/glob/question.in @@ -0,0 +1,7 @@ +; test ? + +root=true + +[som?.c] +key=value + diff --git a/plugins/editorconfig/tests/glob/star.in b/plugins/editorconfig/tests/glob/star.in new file mode 100644 index 0000000..c7d874f --- /dev/null +++ b/plugins/editorconfig/tests/glob/star.in @@ -0,0 +1,12 @@ +; test * + +root=true + +[a*e.c] +key=value + +[Bar/*] +keyb=valueb + +[*] +keyc=valuec diff --git a/plugins/editorconfig/tests/glob/star_star.in b/plugins/editorconfig/tests/glob/star_star.in new file mode 100644 index 0000000..c8f2c99 --- /dev/null +++ b/plugins/editorconfig/tests/glob/star_star.in @@ -0,0 +1,15 @@ +; test ** + +root=true + +[a**z.c] +key1=value1 + +[b/**z.c] +key2=value2 + +[c**/z.c] +key3=value3 + +[d/**/z.c] +key4=value4 diff --git a/plugins/editorconfig/tests/glob/utf8char.in b/plugins/editorconfig/tests/glob/utf8char.in new file mode 100644 index 0000000..6fe89b0 --- /dev/null +++ b/plugins/editorconfig/tests/glob/utf8char.in @@ -0,0 +1,6 @@ +; test EditorConfig files with UTF-8 characters larger than 127 + +root = true + +[中文.txt] +key = value diff --git a/plugins/editorconfig/tests/init.lua b/plugins/editorconfig/tests/init.lua new file mode 100644 index 0000000..654067b --- /dev/null +++ b/plugins/editorconfig/tests/init.lua @@ -0,0 +1,143 @@ +local Parser = require "plugins.editorconfig.parser" + +local tests = {} + +---@class tests.test +---@field name string Name of test +---@field config string Path to config file +---@field in_match string A path to test against the config +---@field out_match string A regex to match against the result + +---Registered tests +---@type tests.test[] +tests.list = {} + +--- parsers cache +---@type table<string,plugins.editorconfig.parser> +local parsers = {} +setmetatable(parsers, { + __index = function(t, k) + local v = rawget(t, k) + if v == nil then + v = Parser.new(k) + rawset(t, k, v) + end + return v + end +}) + +---Adds color to given text on non windows systems. +---@param text string +---@param color "red" | "green" | "yellow" +---@return string colorized_text +local function colorize(text, color) + if PLATFORM ~= "Windows" then + if color == "green" then + return "\27[92m"..text.."\27[0m" + elseif color == "red" then + return "\27[91m"..text.."\27[0m" + elseif color == "yellow" then + return "\27[93m"..text.."\27[0m" + end + end + return text +end + +local PASSED = colorize("PASSED", "green") +local FAILED = colorize("FAILED", "red") + +---Runs an individual test (executed by tests.run()) +---@param name string Test name +---@param config_path string Relative path to tests diretory for a [config].in +---@param in_match string Filename to match +---@param out_match string | table Result to match regex +function tests.check_config(name, config_path, in_match, out_match, pos, total) + if type(out_match) == "string" then + out_match = { out_match } + end + local parser = parsers[USERDIR .. "/plugins/editorconfig/tests/" .. config_path] + local config = parser:getConfigString(in_match) + local passed = true + for _, match in ipairs(out_match) do + if not regex.match(match, config) then + passed = false + break + end + end + if pos then + pos = "[" .. pos .. "/" .. total .. "] " + else + pos = "" + end + if passed then + print(pos .. string.format("%s - %s - '%s': %s", name, in_match, config_path, PASSED)) + else + print(pos .. string.format("%s - %s - '%s': %s", name, in_match, config_path, FAILED)) + print(config) + end + return passed +end + +---Register a new test to be run later. +---@param name string Test name +---@param config_path string Relative path to tests diretory for a [config].in +---@param in_match string Filename to match +---@param out_match string | table Result to match regex +function tests.add(name, config_path, in_match, out_match) + table.insert(tests.list, { + name = name, + config = config_path, + in_match = in_match, + out_match = out_match + }) +end + +---Runs all registered tests and outputs the results to terminal. +function tests.run() + print "=========================================================" + print "Running Tests" + print "=========================================================" + local failed = 0 + local passed = 0 + local total = #tests.list + for i, test in ipairs(tests.list) do + local res = tests.check_config( + test.name, test.config, test.in_match, test.out_match, i, total + ) + if res then passed = passed + 1 else failed = failed + 1 end + end + print "=========================================================" + print ( + string.format( + "%s %s %s", + colorize("Total tests: " .. #tests.list, "yellow"), + colorize("Passed: " .. passed, "green"), + colorize("Failed: " .. failed, "red") + ) + ) + print "=========================================================" +end + +function tests.add_parser(config_path) + return parsers[config_path] +end + +function tests.run_parsers() + print "=========================================================" + print "Running Parsers" + print "=========================================================" + + for config, parser in pairs(parsers) do + print "---------------------------------------------------------" + print(string.format("%s results:", config)) + for _, section in ipairs(parser.sections) do + print(string.format("\nPath expression: %s", section.rule.expression)) + print(string.format("Regex: %s", section.rule.regex)) + print(string.format("Negation: %s", section.rule.negation and "true" or "false")) + print(string.format("Ranges: %s\n", section.rule.ranges and #section.rule.ranges or "0")) + end + print "---------------------------------------------------------" + end +end + +return tests diff --git a/plugins/editorconfig/tests/parser/basic.in b/plugins/editorconfig/tests/parser/basic.in new file mode 100644 index 0000000..3033b9a --- /dev/null +++ b/plugins/editorconfig/tests/parser/basic.in @@ -0,0 +1,16 @@ +[*.a] +option1=value1 + +; repeat section +[*.a] +option2=value2 + +[*.b] +option1 = a +option2 = a + +[b.b] +option2 = b + +[*.b] +option1 = c diff --git a/plugins/editorconfig/tests/parser/bom.in b/plugins/editorconfig/tests/parser/bom.in new file mode 100644 index 0000000..8bde201 --- /dev/null +++ b/plugins/editorconfig/tests/parser/bom.in @@ -0,0 +1,6 @@ +; test EditorConfig files with BOM + +root = true + +[*] +key = value diff --git a/plugins/editorconfig/tests/parser/comments.in b/plugins/editorconfig/tests/parser/comments.in new file mode 100644 index 0000000..c49fba8 --- /dev/null +++ b/plugins/editorconfig/tests/parser/comments.in @@ -0,0 +1,47 @@ +; test comments + +root = true + +[test3.c] +; Comment before properties ignored +key=value + +[test4.c] +key1=value1 +; Comment between properties ignored +key2=value2 + +; Semicolon or hash at end of value read as part of value +[test5.c] +key1=value; not comment +key2=value # not comment + +; Backslash before a semicolon or hash is part of the value +[test6.c] +key1=value \; not comment +key2=value \# not comment + +; Escaped semicolon in section name +[test\;.c] +key=value + +[test9.c] +# Comment before properties ignored +key=value + +[test10.c] +key1=value1 +# Comment between properties ignored +key2=value2 + +# Octothorpe at end of value read as part of value +[test11.c] +key=value# not comment + +# Escaped octothorpe in value +[test12.c] +key=value \# not comment + +# Escaped octothorpe in section name +[test\#.c] +key=value diff --git a/plugins/editorconfig/tests/parser/comments_and_newlines.in b/plugins/editorconfig/tests/parser/comments_and_newlines.in new file mode 100644 index 0000000..35fc023 --- /dev/null +++ b/plugins/editorconfig/tests/parser/comments_and_newlines.in @@ -0,0 +1,4 @@ + +# Just comments + +# ... and newlines diff --git a/plugins/editorconfig/tests/parser/comments_only.in b/plugins/editorconfig/tests/parser/comments_only.in new file mode 100644 index 0000000..9592ed2 --- /dev/null +++ b/plugins/editorconfig/tests/parser/comments_only.in @@ -0,0 +1 @@ +# Just a comment, nothing else
\ No newline at end of file diff --git a/plugins/editorconfig/tests/parser/crlf.in b/plugins/editorconfig/tests/parser/crlf.in new file mode 100644 index 0000000..ec582d2 --- /dev/null +++ b/plugins/editorconfig/tests/parser/crlf.in @@ -0,0 +1,6 @@ +; test EditorConfig files with CRLF line separators
+
+root = true
+
+[*]
+key = value
diff --git a/plugins/editorconfig/tests/parser/empty.in b/plugins/editorconfig/tests/parser/empty.in new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/editorconfig/tests/parser/empty.in diff --git a/plugins/editorconfig/tests/parser/init.lua b/plugins/editorconfig/tests/parser/init.lua new file mode 100644 index 0000000..cf473e5 --- /dev/null +++ b/plugins/editorconfig/tests/parser/init.lua @@ -0,0 +1,107 @@ +local tests = require "plugins.editorconfig.tests" + +-- Basic parser tests + +-- test repeat sections +tests.add("repeat_sections_ML", "parser/basic.in", "a.a", "option1=value1[ \t]*[\n\r]+option2=value2[ \t\n\r]*") +tests.add("basic_cascade_ML", "parser/basic.in", "b.b", "option1=c[ \t]*[\n\r]+option2=b[ \t\n\r]*") + +-- Tests for whitespace parsing + +-- test no whitespaces in property assignment +tests.add("no_whitespace", "parser/whitespace.in", "test1.c", "^key=value[ \t\n\r]*$") + +-- test single spaces around equals sign +tests.add("single_spaces_around_equals", "parser/whitespace.in", "test2.c", + "^key=value[ \t\n\r]*$") + +-- test multiple spaces around equals sign +tests.add("multiple_spaces_around_equals", "parser/whitespace.in", "test3.c", + "^key=value[ \t\n\r]*$") + +-- test spaces before property name +tests.add("spaces_before_property_name", "parser/whitespace.in", "test4.c", + "^key=value[ \t\n\r]*$") + +-- test spaces before after property value +tests.add("spaces_after_property_value", "parser/whitespace.in", "test5.c", + "^key=value[ \t\n\r]*$") + +-- test blank lines between properties +tests.add("blank_lines_between_properties_ML", "parser/whitespace.in", "test6.c", + "key1=value1[ \t]*[\n\r]+key2=value2[ \t\n\r]*") + +-- test spaces in section name +tests.add("spaces_in_section_name", "parser/whitespace.in", " test 7 ", + "^key=value[ \t\n\r]*$") + +-- test spaces before section name are ignored +tests.add("spaces_before_section_name", "parser/whitespace.in", "test8.c", + "^key=value[ \t\n\r]*$") + +-- test spaces after section name +tests.add("spaces_after_section_name", "parser/whitespace.in", "test9.c", "^key=value[ \t\n\r]*$") + +-- test spaces at beginning of line between properties +tests.add("spaces_before_middle_property_ML", "parser/whitespace.in", "test10.c", + "key1=value1[ \t]*[\n\r]+key2=value2[ \t]*[\n\r]+key3=value3[ \t\n\r]*") + +-- Tests for comment parsing + +-- test comments ignored before properties +tests.add("comment_before_props", "parser/comments.in", "test3.c", + "^key=value[ \t\n\r]*$") + +-- test comments ignored between properties +tests.add("comment_between_props_ML", "parser/comments.in", "test4.c", + "key1=value1[ \t]*[\n\r]+key2=value2[ \t\n\r]*") + +-- test semicolons and hashes at end of property value are included in value +tests.add("semicolon_or_hash_in_property", "parser/comments.in", "test5.c", + "^key1=value; not comment[\n\r]+key2=value # not comment[ \t\n\r]*$") + +-- test that backslashes before semicolons and hashes in property values +-- are included in value. +-- NOTE: [\\] matches a single literal backslash. +tests.add("backslashed_semicolon_or_hash_in_property", "parser/comments.in", "test6.c", + "^key1=value [\\\\]; not comment[\n\r]+key2=value [\\\\]# not comment[ \t\n\r]*$") + +-- test escaped semicolons are included in section names +tests.add("escaped_semicolon_in_section", "parser/comments.in", "test;.c", + "^key=value[ \t\n\r]*$") + +-- test octothorpe comments ignored before properties +tests.add("octothorpe_comment_before_props", "parser/comments.in", "test9.c", + "^key=value[ \t\n\r]*$") + +-- test octothorpe comments ignored between properties +tests.add("octothorpe_comment_between_props_ML", "parser/comments.in", "test10.c", + "key1=value1[ \t]*[\n\r]+key2=value2[ \t\n\r]*") + +-- test octothorpe at end of property value are included in value +tests.add("octothorpe_in_value", "parser/comments.in", "test11.c", + "^key=value# not comment[ \t\n\r]*$") + +-- test escaped octothorpes are included in section names +tests.add("escaped_octothorpe_in_section", "parser/comments.in", "test#.c", + "^key=value[ \t\n\r]*$") + +-- test EditorConfig files with BOM at the head +tests.add("bom_at_head", "parser/bom.in", "a.c", "^key=value[ \t\n\r]*$") + +-- test EditorConfig files with CRLF line separators +tests.add("crlf_linesep", "parser/crlf.in", "a.c", "^key=value[ \t\n\r]*$") + +-- Test minimum supported lengths of section name, key and value +tests.add("min_supported_key_length", "parser/limits.in", "test1", + "^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=v1024[ \t\n\r]*$") +tests.add("min_supported_value_length", "parser/limits.in", "test2", + "^k4096=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[ \t\n\r]*$") +tests.add("min_supported_section_name_length", "parser/limits.in", "test3", + "^k1024=v1024[ \t\n\r]*$") + +-- Empty .editorconfig files +tests.add("empty_editorconfig_file", "parser/empty.in", "test4", "^[ \t\n\r]*$") +tests.add("newlines_only_editorconfig_file", "parser/newlines_only.in", "test4", "^[ \t\n\r]*$") +tests.add("comments_only_editorconfig_file", "parser/comments_only.in", "test4", "^[ \t\n\r]*$") +tests.add("comments_and_newlines_editorconfig_file", "parser/comments_and_newlines.in", "test4", "^[ \t\n\r]*$") diff --git a/plugins/editorconfig/tests/parser/limits.in b/plugins/editorconfig/tests/parser/limits.in new file mode 100644 index 0000000..d768a8c --- /dev/null +++ b/plugins/editorconfig/tests/parser/limits.in @@ -0,0 +1,13 @@ +root = true + +; minimum supported key length of 1024 characters +[test1] +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=v1024 + +; minimum supported value length of 4096 characters +[test2] +k4096=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +; minimum supported section name length of 1024 characters (excluding [] brackets) +[{test3,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}] +k1024=v1024 diff --git a/plugins/editorconfig/tests/parser/newlines_only.in b/plugins/editorconfig/tests/parser/newlines_only.in new file mode 100644 index 0000000..139597f --- /dev/null +++ b/plugins/editorconfig/tests/parser/newlines_only.in @@ -0,0 +1,2 @@ + + diff --git a/plugins/editorconfig/tests/parser/whitespace.in b/plugins/editorconfig/tests/parser/whitespace.in new file mode 100644 index 0000000..d1f3c5f --- /dev/null +++ b/plugins/editorconfig/tests/parser/whitespace.in @@ -0,0 +1,48 @@ +; test whitespace usage + +root = true + +; no whitespace +[test1.c] +key=value + +; spaces around equals +[test2.c] +key = value + +; lots of space after equals +[test3.c] +key = value + +; spaces before property name +[test4.c] + key=value + +; spaces after property value +[test5.c] +key=value + +; blank lines between properties +[test6.c] + +key1=value1 + +key2=value2 + +; spaces in section name +[ test 7 ] +key=value + +; spaces before section name + [test8.c] +key=value + +; spaces after section name +[test9.c] +key=value + +; spacing before middle property +[test10.c] +key1=value1 + key2=value2 +key3=value3 diff --git a/plugins/editorconfig/tests/properties/indent_size_default.in b/plugins/editorconfig/tests/properties/indent_size_default.in new file mode 100644 index 0000000..809fc3f --- /dev/null +++ b/plugins/editorconfig/tests/properties/indent_size_default.in @@ -0,0 +1,11 @@ +root = true + +[test.c] +indent_style = tab + +[test2.c] +indent_style = space + +[test3.c] +indent_style = tab +tab_width = 2 diff --git a/plugins/editorconfig/tests/properties/init.lua b/plugins/editorconfig/tests/properties/init.lua new file mode 100644 index 0000000..4ae22d0 --- /dev/null +++ b/plugins/editorconfig/tests/properties/init.lua @@ -0,0 +1,42 @@ +local tests = require "plugins.editorconfig.tests" + +-- test tab_width default +tests.add("tab_width_default_ML", "properties/tab_width_default.in", "test.c", + "indent_size=4[ \t]*[\n\r]+indent_style=space[ \t]*[\n\r]+tab_width=4[\t\n\r]*") + +-- Tab_width should not be set to any value if indent_size is "tab" and +-- tab_width is not set +tests.add("tab_width_default_indent_size_tab_ML", "properties/tab_width_default.in", + "test2.c", "indent_size=tab[ \t]*[\n\r]+indent_style=tab[ \t\n\r]*") + +-- Test indent_size default. When indent_style is "tab", indent_size defaults to +-- "tab". +tests.add("indent_size_default_ML", "properties/indent_size_default.in", "test.c", + "indent_size=tab[ \t]*[\n\r]+indent_style=tab[ \t\n\r]*") + +-- Test indent_size default. When indent_style is "space", indent_size has no +-- default value. +tests.add("indent_size_default_space", "properties/indent_size_default.in", "test2.c", + "^indent_style=space[ \t\n\r]*$") + +-- Test indent_size default. When indent_style is "tab" and tab_width is set, +-- indent_size should default to tab_width +tests.add("indent_size_default_with_tab_width_ML", + "properties/indent_size_default.in", "test3.c", + "indent_size=2[ \t]*[\n\r]+indent_style=tab[ \t]*[\n\r]+tab_width=2[ \t\n\r]*") + +-- test that same property values are lowercased (v0.9.0 properties) +tests.add("lowercase_values1_ML", "properties/lowercase_values.in", "test1.c", + "end_of_line=crlf[ \t]*[\n\r]+indent_style=space[ \t\n\r]*") + +-- test that same property values are lowercased (v0.9.0 properties) +tests.add("lowercase_values2_ML", "properties/lowercase_values.in", "test2.c", + "charset=utf-8[ \t]*[\n\r]+insert_final_newline=true[ \t]*[\n\r]+trim_trailing_whitespace=false[ \t\n\r]*$") + +-- test that same property values are not lowercased +tests.add("lowercase_values3", "properties/lowercase_values.in", "test3.c", + "^test_property=TestValue[ \t\n\r]*$") + +-- test that all property names are lowercased +tests.add("lowercase_names", "properties/lowercase_names.in", "test.c", + "^testproperty=testvalue[ \t\n\r]*$") diff --git a/plugins/editorconfig/tests/properties/lowercase_names.in b/plugins/editorconfig/tests/properties/lowercase_names.in new file mode 100644 index 0000000..253ea8b --- /dev/null +++ b/plugins/editorconfig/tests/properties/lowercase_names.in @@ -0,0 +1,6 @@ +; test that property names are lowercased + +root = true + +[test.c] +TestProperty = testvalue diff --git a/plugins/editorconfig/tests/properties/lowercase_values.in b/plugins/editorconfig/tests/properties/lowercase_values.in new file mode 100644 index 0000000..1730bb2 --- /dev/null +++ b/plugins/editorconfig/tests/properties/lowercase_values.in @@ -0,0 +1,15 @@ +; test property name lowercasing + +root = true + +[test1.c] +indent_style = Space +end_of_line = CRLF + +[test2.c] +insert_final_newline = TRUE +trim_trailing_whitespace = False +charset = UTF-8 + +[test3.c] +test_property = TestValue diff --git a/plugins/editorconfig/tests/properties/tab_width_default.in b/plugins/editorconfig/tests/properties/tab_width_default.in new file mode 100644 index 0000000..3084607 --- /dev/null +++ b/plugins/editorconfig/tests/properties/tab_width_default.in @@ -0,0 +1,9 @@ +root = true + +[test.c] +indent_style = space +indent_size = 4 + +[test2.c] +indent_style = tab +indent_size = tab |