diff --git a/lua/codesettings/build/cli.lua b/lua/codesettings/build/cli.lua index 74ccb33..fd28cc6 100644 --- a/lua/codesettings/build/cli.lua +++ b/lua/codesettings/build/cli.lua @@ -3,12 +3,14 @@ local build_targets = { annotations = require('codesettings.build.annotations'), config = require('codesettings.build.config-schema'), doc = require('codesettings.build.doc'), + terminals = require('codesettings.build.terminal-objects'), } -- Define build order and dependencies -local build_order = { 'schemas', 'annotations', 'config', 'doc' } +local build_order = { 'schemas', 'terminals', 'annotations', 'config', 'doc' } local build_dependencies = { + terminals = { 'schemas' }, annotations = { 'schemas' }, doc = { 'schemas' }, } diff --git a/lua/codesettings/build/schemas.lua b/lua/codesettings/build/schemas.lua index 703c1c6..bee5ef0 100644 --- a/lua/codesettings/build/schemas.lua +++ b/lua/codesettings/build/schemas.lua @@ -97,6 +97,65 @@ function M.get_schemas() return ret end +--- Collect all terminal object property paths from a schema. +--- Terminal objects are properties with type="object" but no "properties" field, +--- meaning their keys are arbitrary user data and should not be dot-expanded. +---@param schema table JSON schema +---@param prefix string? current property path prefix +---@param terminals table table to collect terminal paths +local function collect_terminal_objects(schema, prefix, terminals) + if type(schema) ~= 'table' then + return + end + + local props = schema.properties + if type(props) ~= 'table' then + return + end + + for name, def in pairs(props) do + if type(def) == 'table' then + local full_path = prefix and (prefix .. '.' .. name) or name + + -- Check if this is a terminal object: + -- - type is "object" + -- - no "properties" field (free-form dictionary) + if def.type == 'object' and not def.properties then + terminals[full_path] = true + end + + -- Recurse if it has nested properties + if def.properties then + collect_terminal_objects(def, full_path, terminals) + end + end + end +end + +--- Generate a lookup table of all terminal object paths across all LSP schemas. +--- Returns a table mapping property paths to true for paths that should not have +--- their keys dot-expanded (e.g., "yaml.schemas"). +---@return table terminal object paths +function M.generate_terminal_objects_cache() + local schemas = M.get_schemas() + local terminals = {} + + for _, schema_meta in pairs(schemas) do + if Util.exists(schema_meta.settings_file) then + local ok, data = pcall(vim.fn.readfile, schema_meta.settings_file) + if ok and type(data) == 'table' then + local json_str = table.concat(data, '\n') + local ok2, schema_json = pcall(vim.fn.json_decode, json_str) + if ok2 and type(schema_json) == 'table' then + collect_terminal_objects(schema_json, nil, terminals) + end + end + end + end + + return terminals +end + --- A map of LSP server schemas that require special handling for non-common configuration layouts. local SpecialCases = { nixd = function(json) diff --git a/lua/codesettings/build/terminal-objects.lua b/lua/codesettings/build/terminal-objects.lua new file mode 100644 index 0000000..6ac576f --- /dev/null +++ b/lua/codesettings/build/terminal-objects.lua @@ -0,0 +1,43 @@ +-- Build script to generate cache of terminal object paths +local Schemas = require('codesettings.build.schemas') +local Util = require('codesettings.util') + +local relpath = 'lua/codesettings/generated/terminal-objects.lua' + +local M = {} + +function M.build() + if #arg == 0 then + error('This function is part of a build tool and should not be called directly!') + end + + print('Generating terminal objects cache...') + local terminals = Schemas.generate_terminal_objects_cache() + + -- Generate Lua file with the cache + local lines = { + '-- stylua: ignore', + '-- This file contains a lookup table of property paths that represent', + '-- terminal objects (type=object with no properties field).', + '-- Keys inside these objects should not be dot-expanded.', + '', + '---@type table', + 'return {', + } + + local paths = vim.tbl_keys(terminals) + table.sort(paths) + + for _, path in ipairs(paths) do + table.insert(lines, string.format(' [%q] = true,', path)) + end + + table.insert(lines, '}') + table.insert(lines, '') + + Util.write_file(Util.path(relpath), table.concat(lines, '\n')) + + print(string.format('Generated %s with %d terminal object paths', relpath, #paths)) +end + +return M diff --git a/lua/codesettings/generated/terminal-objects.lua b/lua/codesettings/generated/terminal-objects.lua new file mode 100644 index 0000000..0261897 --- /dev/null +++ b/lua/codesettings/generated/terminal-objects.lua @@ -0,0 +1,64 @@ +-- stylua: ignore +-- This file contains a lookup table of property paths that represent +-- terminal objects (type=object with no properties field). +-- Keys inside these objects should not be dot-expanded. + +---@type table +return { + ["Lua.format.defaultConfig"] = true, + ["Lua.nameStyle.config"] = true, + ["Lua.runtime.special"] = true, + ["ada.scenarioVariables"] = true, + ["csharp.debug.sourceFileMap"] = true, + ["dart.customDevTools.env"] = true, + ["dart.env"] = true, + ["dart.getDartSdkCommand.env"] = true, + ["dart.getFlutterSdkCommand.env"] = true, + ["dart.mcpServerTools"] = true, + ["deno.env"] = true, + ["deno.suggest.imports.hosts"] = true, + ["dotnet.unitTestDebuggingOptions.sourceFileMap"] = true, + ["dotnet.unitTestDebuggingOptions.sourceLinkOptions"] = true, + ["elixirLS.envVariables"] = true, + ["eslint.codeActionsOnSave.options"] = true, + ["eslint.options"] = true, + ["gopls.env"] = true, + ["gopls.semanticTokenModifiers"] = true, + ["gopls.semanticTokenTypes"] = true, + ["haskell.serverEnvironment"] = true, + ["haskell.toolchain"] = true, + ["java.project.referencedLibraries.sources"] = true, + ["ltex.bibtex.fields"] = true, + ["ltex.latex.commands"] = true, + ["ltex.latex.environments"] = true, + ["ltex.markdown.nodes"] = true, + ["luau-lsp.fflags.override"] = true, + ["luau-lsp.require.directoryAliases"] = true, + ["luau-lsp.require.fileAliases"] = true, + ["luau-lsp.types.definitionFiles"] = true, + ["perl.env"] = true, + ["perlnavigator.perlEnv"] = true, + ["powershell.powerShellAdditionalExePaths"] = true, + ["rust-analyzer.cargo.extraEnv"] = true, + ["rust-analyzer.check.extraEnv"] = true, + ["rust-analyzer.completion.snippets.custom"] = true, + ["rust-analyzer.debug.engineSettings"] = true, + ["rust-analyzer.diagnostics.remapPrefix"] = true, + ["rust-analyzer.lru.query.capacities"] = true, + ["rust-analyzer.procMacro.ignored"] = true, + ["rust.rust-analyzer"] = true, + ["sonarlint.analyzerProperties"] = true, + ["sonarlint.rules"] = true, + ["stylelintplus.config"] = true, + ["stylelintplus.configOverrides"] = true, + ["svelte.plugin.svelte.compilerWarnings"] = true, + ["swift.excludePathsFromActivation"] = true, + ["swift.pluginPermissions"] = true, + ["swift.swiftEnvironmentVariables"] = true, + ["swift.testEnvironmentVariables"] = true, + ["tailwindCSS.includeLanguages"] = true, + ["tinymist.preview.sysInputs"] = true, + ["vetur.format.defaultFormatterOptions.js-beautify-html"] = true, + ["vetur.grammar.customBlocks"] = true, + ["yaml.schemas"] = true, +} diff --git a/lua/codesettings/settings.lua b/lua/codesettings/settings.lua index e1f9eaa..9174d99 100644 --- a/lua/codesettings/settings.lua +++ b/lua/codesettings/settings.lua @@ -1,5 +1,6 @@ local Util = require('codesettings.util') local Extensions = require('codesettings.extensions') +local TerminalObjects = require('codesettings.generated.terminal-objects') local M = {} @@ -59,21 +60,33 @@ end ---Expand a table with dotted keys into a nested table structure ---@param tbl table the table to expand +---@param current_path string? internal recursion use: current property path for tracking terminal objects ---@return table expanded the expanded table -function M.expand(tbl) +function M.expand(tbl, current_path) if type(tbl) ~= 'table' then return tbl end + -- Check if we're inside a terminal object (free-form dictionary) + -- If so, do not expand dotted keys in this table + local is_terminal = current_path and TerminalObjects[current_path] + local out = {} for key, value in pairs(tbl) do local v = value + + -- Build the path for this key + local key_path = current_path and (current_path .. '.' .. key) or key + -- Recurse into map-like tables, but never into JSON Schema "properties" tables. if is_map(v) and key ~= 'properties' then - v = M.expand(v) + v = M.expand(v, key_path) end - if type(key) == 'string' and key:find('%.') then + -- Only expand dotted keys if: + -- 1. The key contains a dot + -- 2. We're NOT inside a terminal object + if type(key) == 'string' and key:find('%.') and not is_terminal then local parts = {} for part in key:gmatch('[^.]+') do parts[#parts + 1] = part diff --git a/schemas/dartls.json b/schemas/dartls.json index 0e3d14a..13d9a2f 100644 --- a/schemas/dartls.json +++ b/schemas/dartls.json @@ -1213,6 +1213,7 @@ "type": "boolean" }, "default": { + "analyze_files": false, "run_tests": false }, "markdownDescription": "A map of MCP tool names to booleans to enable/disable specific tools from the Dart MCP server. Tools set to `false` will be excluded (if supported). By default, tools that overlap with built-in VS Code functionality will be excluded.", diff --git a/schemas/eslint.json b/schemas/eslint.json index bf58876..806a064 100644 --- a/schemas/eslint.json +++ b/schemas/eslint.json @@ -259,7 +259,8 @@ "jsonc", "css", "glimmer-js", - "glimmer-ts" + "glimmer-ts", + "svelte" ], "description": "An array of language ids for which the extension should probe if support is installed.", "items": { diff --git a/schemas/intelephense.json b/schemas/intelephense.json index 70b1d92..c45ac35 100644 --- a/schemas/intelephense.json +++ b/schemas/intelephense.json @@ -350,6 +350,24 @@ "scope": "window", "type": "boolean" }, + "intelephense.inlayHint.parameterNames": { + "default": true, + "description": "Will show inlay hints for call argument parameter names if named arguments are not already in use.", + "scope": "window", + "type": "boolean" + }, + "intelephense.inlayHint.parameterTypes": { + "default": true, + "description": "Will show inlay hints for anonymous function declaration parameter types if not already declared.", + "scope": "window", + "type": "boolean" + }, + "intelephense.inlayHint.returnTypes": { + "default": true, + "description": "Will show an inlay hint for call declaration return type if not already declared.", + "scope": "window", + "type": "boolean" + }, "intelephense.licenceKey": { "description": "DEPRECATED. Don't use this. Go to command palette and search for enter licence key.", "scope": "application", diff --git a/spec/settings_spec.lua b/spec/settings_spec.lua index 9bbedfe..f749f0c 100644 --- a/spec/settings_spec.lua +++ b/spec/settings_spec.lua @@ -4,7 +4,7 @@ local Settings = require('codesettings.settings') describe('Settings.path()', function() it('returns empty table for nil/empty', function() - assert.same({}, Settings.path(nil)) + assert.same({}, Settings.path(nil)) ---@diagnostic disable-line: param-type-mismatch assert.same({}, Settings.path('')) end) @@ -13,7 +13,7 @@ describe('Settings.path()', function() end) it('wraps non-string keys', function() - assert.same({ 123 }, Settings.path(123)) + assert.same({ 123 }, Settings.path(123)) ---@diagnostic disable-line: param-type-mismatch end) end) @@ -63,7 +63,7 @@ end) describe('Settings.expand()', function() it('passes non-table through', function() - assert.equal(5, Settings.expand(5)) + assert.equal(5, Settings.expand(5)) ---@diagnostic disable-line: param-type-mismatch end) it('expands dotted keys into nested tables', function() @@ -208,7 +208,7 @@ end) describe('Settings.expand()', function() it('passes non-table through', function() - assert.equal(5, Settings.expand(5)) + assert.equal(5, Settings.expand(5)) ---@diagnostic disable-line: param-type-mismatch end) it('expands dotted keys into nested tables', function() @@ -277,4 +277,83 @@ describe('Settings.expand()', function() }, }, expanded) end) + + it('does not expand keys inside terminal objects (e.g., yaml.schemas)', function() + -- yaml.schemas is a terminal object (type=object, no properties) + -- Keys inside it should not be dot-expanded + local expanded = Settings.expand({ + ['yaml.schemas'] = { + ['./some/relative/schema.json'] = 'relpath/*', + ['https://example.com/schema.json'] = 'config/*.yaml', + ['../parent/schema.json'] = 'schemas/*', + }, + }) + assert.same({ + yaml = { + schemas = { + ['./some/relative/schema.json'] = 'relpath/*', + ['https://example.com/schema.json'] = 'config/*.yaml', + ['../parent/schema.json'] = 'schemas/*', + }, + }, + }, expanded) + end) + + it('does not expand keys inside terminal objects when already nested', function() + local expanded = Settings.expand({ + yaml = { + schemas = { + ['./some/relative/schema.json'] = 'relpath/*', + }, + }, + }) + assert.same({ + yaml = { + schemas = { + ['./some/relative/schema.json'] = 'relpath/*', + }, + }, + }, expanded) + end) + + it('still expands dotted keys in non-terminal nested objects', function() + -- Regular nested objects should still have their dotted keys expanded + local expanded = Settings.expand({ + yaml = { + format = { + ['some.nested.key'] = true, + }, + }, + }) + assert.same({ + yaml = { + format = { + some = { + nested = { + key = true, + }, + }, + }, + }, + }, expanded) + end) + + it('handles terminal objects with multiple levels of nesting', function() + local expanded = Settings.expand({ + ['yaml.schemas'] = { + ['file.with.dots.json'] = 'pattern', + }, + ['yaml.format.enable'] = true, + }) + assert.same({ + yaml = { + schemas = { + ['file.with.dots.json'] = 'pattern', + }, + format = { + enable = true, + }, + }, + }, expanded) + end) end)