Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: golangci-lint

on:
pull_request:
workflow_dispatch:

jobs:
golangci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v6
with:
go-version-file: go.mod

- name: Install golangci-lint
run: |
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"

- name: Run golangci-lint
run: |
golangci-lint version
golangci-lint run
18 changes: 18 additions & 0 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: PR Tests

on:
pull_request:
workflow_dispatch:

jobs:
go-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Run Go tests
run: go test ./...
29 changes: 29 additions & 0 deletions .github/workflows/vscode-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: VS Code Extension Quality

on:
pull_request:
workflow_dispatch:

jobs:
vscode-quality:
runs-on: ubuntu-latest
defaults:
run:
working-directory: vscode-make-ls
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: vscode-make-ls/package-lock.json

- name: Install dependencies
run: npm ci

- name: Check formatting
run: npm run format:check

- name: Check types and build
run: npm run lint
15 changes: 15 additions & 0 deletions internal/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,21 @@ func TestDefinitionFromUnexportVarName(t *testing.T) {
assert.Equal(t, 0, locs[0].Range.Start.Line) // FOO defined on line 0
}

func TestDefinitionFromExportVarNameToOverrideVar(t *testing.T) {
harness := newHarness(t)

input := "override KBUILD_VERBOSE :=\nexport quiet Q KBUILD_VERBOSE\n"
require.NoError(t, harness.DidOpen(testURI, "makefile", input))

// Cursor on "KBUILD_VERBOSE" in "export quiet Q KBUILD_VERBOSE"
locs, err := harness.Definition(testURI, 1, 15)
require.NoError(t, err)
require.Len(t, locs, 1)
assert.Equal(t, 0, locs[0].Range.Start.Line)
assert.Equal(t, 9, locs[0].Range.Start.Character)
assert.Equal(t, 23, locs[0].Range.End.Character)
}

func TestReferencesForTarget(t *testing.T) {
harness := newHarness(t)

Expand Down
27 changes: 20 additions & 7 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func (p *parser) parseLine() {
Flavour: model.FlavourForOp(model.VarOp(m[3])),
TargetScope: targetName,
Range: lineRange(startLine, 0, len(line)),
NameRange: nameRange(startLine, trimmed, m[2]),
NameRange: nameRangeInSegment(startLine, line, trimmed, m[2]),
Refs: extractVarRefs(m[4], startLine),
}
p.variables = append(p.variables, v)
Expand Down Expand Up @@ -333,7 +333,7 @@ func (p *parser) parseLine() {
IsDoubleColon: isDouble,
DocComment: p.buildDocComment(),
Range: lineRange(startLine, 0, len(line)),
NameRange: nameRange(startLine, trimmed, namesPart),
NameRange: nameRangeInSegment(startLine, line, trimmed, namesPart),
}
p.targets = append(p.targets, t)
p.currentTarget = t
Expand Down Expand Up @@ -363,7 +363,7 @@ func (p *parser) tryParseVarAssign(s string, startLine int, fullLine string) *mo
Op: op,
Flavour: model.FlavourForOp(op),
Range: lineRange(startLine, 0, len(fullLine)),
NameRange: nameRange(startLine, s, strings.TrimSpace(m[2])),
NameRange: nameRangeInSegment(startLine, fullLine, s, strings.TrimSpace(m[2])),
Refs: extractVarRefs(m[4], startLine),
}
}
Expand Down Expand Up @@ -610,13 +610,26 @@ func lineRange(line, startChar, endChar int) lsp.Range {
}

func nameRange(line int, fullLine, name string) lsp.Range {
idx := strings.Index(fullLine, name)
return nameRangeInSegment(line, fullLine, fullLine, name)
}

func nameRangeInSegment(line int, fullLine, segment, name string) lsp.Range {
base := strings.Index(fullLine, segment)
if base < 0 {
base = 0
}
idx := strings.Index(segment, name)
if idx < 0 {
idx = 0
idx = strings.Index(fullLine, name)
base = 0
if idx < 0 {
idx = 0
}
}
col := base + idx
return lsp.Range{
Start: lsp.Position{Line: line, Character: idx},
End: lsp.Position{Line: line, Character: idx + len(name)},
Start: lsp.Position{Line: line, Character: col},
End: lsp.Position{Line: line, Character: col + len(name)},
}
}

Expand Down
14 changes: 14 additions & 0 deletions internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,20 @@ func TestParseOverrideVar(t *testing.T) {
v := m.Variables[0]
assert.Equal(t, "CC", v.Name)
assert.True(t, v.Override)
assert.Equal(t, 9, v.NameRange.Start.Character)
assert.Equal(t, 11, v.NameRange.End.Character)
}

func TestParseExportVarNameRange(t *testing.T) {
input := "export\tINSTALL_PATH ?= /boot"
m := Parse(testURI, input)

require.Len(t, m.Variables, 1)
v := m.Variables[0]
assert.Equal(t, "INSTALL_PATH", v.Name)
assert.True(t, v.Export)
assert.Equal(t, 7, v.NameRange.Start.Character)
assert.Equal(t, 19, v.NameRange.End.Character)
}

func TestParseVpathDirective(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions vscode-make-ls/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
"scripts": {
"compile": "esbuild src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node --sourcemap",
"watch": "npm run compile -- --watch",
"typecheck": "tsc --noEmit",
"lint": "npm run typecheck && npm run compile",
"format:check": "npx prettier@3.6.2 --check \"src/**/*.ts\" \"scripts/**/*.js\" \"*.json\"",
"package": "npm run compile && node scripts/package.js",
"vscode:prepublish": "esbuild src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node --minify"
},
Expand Down
100 changes: 74 additions & 26 deletions vscode-make-ls/scripts/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,59 @@ const targets = [
{ goos: "linux", goarch: "arm64", vscodeTarget: "linux-arm64" },
];

// Sync version from git tag (v1.2.3 → 1.2.3) so package.json never drifts.
try {
const tag = execSync("git describe --tags --exact-match", { cwd: ROOT, encoding: "utf-8" }).trim();
const version = tag.replace(/^v/, "");
if (/^\d+\.\d+\.\d+$/.test(version)) {
const pkgPath = path.join(EXT_DIR, "package.json");
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
if (pkg.version !== version) {
console.log(`Updating package.json version: ${pkg.version} → ${version}`);
pkg.version = version;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
function parseSemver(tag) {
const m = /^v(\d+)\.(\d+)\.(\d+)$/.exec(tag);
if (!m) return null;
return { tag, parts: [Number(m[1]), Number(m[2]), Number(m[3])] };
}

function compareSemverDesc(a, b) {
for (let i = 0; i < 3; i++) {
if (a.parts[i] !== b.parts[i]) return b.parts[i] - a.parts[i];
}
return 0;
}

function resolveReleaseTag() {
const refTag = process.env.GITHUB_REF_NAME;
if (parseSemver(refTag || "")) {
return refTag;
}

try {
const tags = execSync("git tag --points-at HEAD", {
cwd: ROOT,
encoding: "utf-8",
})
.split("\n")
.map((s) => s.trim())
.filter(Boolean)
.map(parseSemver)
.filter(Boolean)
.sort(compareSemverDesc);
if (tags.length > 0) {
return tags[0].tag;
}
} catch {
// Fall through to package.json version.
}
} catch {
console.log("No exact git tag found, using version from package.json.");

return null;
}

// Sync version from release tag (v1.2.3 → 1.2.3) so package.json never drifts.
const tag = resolveReleaseTag();
if (tag) {
const version = tag.replace(/^v/, "");
const pkgPath = path.join(EXT_DIR, "package.json");
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
if (pkg.version !== version) {
console.log(`Updating package.json version: ${pkg.version} → ${version}`);
pkg.version = version;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
}
} else {
console.log("No release tag found, using version from package.json.");
}

// Copy README from repo root so vsce includes it in the .vsix.
Expand Down Expand Up @@ -60,7 +98,7 @@ for (const t of targets) {
console.log(`Compiling ${t.goos}/${t.goarch}...`);
execSync(
`GOOS=${t.goos} GOARCH=${t.goarch} CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o ${outPath} ./cmd/make-ls`,
{ cwd: ROOT, stdio: "inherit" }
{ cwd: ROOT, stdio: "inherit" },
);

// Make executable.
Expand Down Expand Up @@ -96,17 +134,24 @@ if (process.env.VSCODE_PUBLISH_TOKEN) {
const vsixPath = path.join(EXT_DIR, f);
console.log(` ${f}`);
try {
execSync(`npx vsce publish --pat ${process.env.VSCODE_PUBLISH_TOKEN} --packagePath ${vsixPath}`, {
cwd: EXT_DIR,
stdio: ["ignore", "inherit", "inherit"],
timeout: 120_000,
});
execSync(
`npx vsce publish --pat ${process.env.VSCODE_PUBLISH_TOKEN} --packagePath ${vsixPath}`,
{
cwd: EXT_DIR,
stdio: ["ignore", "inherit", "inherit"],
timeout: 120_000,
},
);
} catch (err) {
console.error(` Failed to publish ${f} to VS Code Marketplace: ${err.message}`);
console.error(
` Failed to publish ${f} to VS Code Marketplace: ${err.message}`,
);
}
}
} else {
console.log("\nSkipping VS Code Marketplace publish (no VSCODE_PUBLISH_TOKEN).");
console.log(
"\nSkipping VS Code Marketplace publish (no VSCODE_PUBLISH_TOKEN).",
);
}

// Publish to Open VSX.
Expand All @@ -116,11 +161,14 @@ if (process.env.OPVSX_PUBLISH_TOKEN) {
const vsixPath = path.join(EXT_DIR, f);
console.log(` ${f}`);
try {
execSync(`npx ovsx publish ${vsixPath} -p ${process.env.OPVSX_PUBLISH_TOKEN}`, {
cwd: EXT_DIR,
stdio: ["ignore", "inherit", "inherit"],
timeout: 120_000,
});
execSync(
`npx ovsx publish ${vsixPath} -p ${process.env.OPVSX_PUBLISH_TOKEN}`,
{
cwd: EXT_DIR,
stdio: ["ignore", "inherit", "inherit"],
timeout: 120_000,
},
);
} catch (err) {
console.error(` Failed to publish ${f} to Open VSX: ${err.message}`);
}
Expand Down
6 changes: 4 additions & 2 deletions vscode-make-ls/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ export function activate(context: ExtensionContext): void {
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: "file", language: "makefile" }],
synchronize: {
fileEvents: workspace.createFileSystemWatcher("**/{Makefile,makefile,GNUmakefile,*.mk,*.mak}"),
fileEvents: workspace.createFileSystemWatcher(
"**/{Makefile,makefile,GNUmakefile,*.mk,*.mak}",
),
},
};

client = new LanguageClient(
"make-ls",
"Makefile Language Server",
serverOptions,
clientOptions
clientOptions,
);

client.start();
Expand Down
Loading