Skip to content

Conversation

colleenmcginnis
Copy link

In addition to autocompletion for subs, this would also add inline warnings when an author hard-codes the value of a sub instead of using the sub key. In my opinion, subs aren't very helpful unless we use them consistently. This would be a light-touch way to encourage consistent use of subs and weed out unnecessary subs without introducing a bunch of errors.

Screenshot 2025-10-06 at 4 39 33 PM

In addition to the inline warnings, this PR would also:

  • Create a central list of products in src/products.ts that can be used to both validate products frontmatter in src/frontmatterSchema.ts and suggest using product subs.
    • There must be a way to get the product list directly from docs-builder, but I think this is better than nothing. 🙃
  • Centralize the logic for getting a list of subs in src/substitutions.ts instead of duplicating the logic in src/substitutionCompletionProvider.ts and src/substitutionHoverProvider.ts.
    • This also includes combining the subs in the docset.yml file in the local workspace with the list of product subs.

Some notes about the logic for choosing which sub to recommend if there are multiple matches:

  • If a local sub defined in a docset.yml file has the same value as a product sub, preference goes to the product sub.
  • If one the value of one sub contains the value of another sub (for example, Fleet/Fleet Server or Elastic Agent/Elastic Agents), preference goes to the longer string.

@theletterf let me know what you think. 🙂

@colleenmcginnis colleenmcginnis self-assigned this Oct 6, 2025
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds inline warnings for missed opportunities to use substitutions (subs) when hardcoded values are detected that match available substitution keys. The goal is to encourage consistent use of substitutions across documentation.

  • Creates a centralized substitution management system with validation warnings
  • Adds product definitions and integrates them with frontmatter validation
  • Consolidates duplicate substitution parsing logic across multiple providers

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/substitutions.ts New centralized substitution management with caching and product integration
src/substitutionValidationProvider.ts New validation provider that warns when hardcoded values could use subs
src/substitutionHoverProvider.ts Refactored to use centralized substitution logic
src/substitutionCompletionProvider.ts Refactored to use centralized substitution logic
src/products.ts New centralized product definitions for validation and substitutions
src/frontmatterSchema.ts Updated to use centralized product list for validation
src/extension.ts Integrated substitution validation into the extension lifecycle

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

}

private validateContent(errors: ValidationError[], document: vscode.TextDocument): void {
const lines = document.getText().split('\n')
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon at end of statement.

Suggested change
const lines = document.getText().split('\n')
const lines = document.getText().split('\n');

Copilot uses AI. Check for mistakes.

Comment on lines +52 to +53
const regex = new RegExp(`(\\W|^)${value}(\\W|$)`, 'gm')
const matches = line.match(regex)
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern may fail with special regex characters in substitution values. The value should be escaped using a regex escape function to prevent regex injection issues.

Copilot uses AI. Check for mistakes.

Comment on lines +52 to +69
const regex = new RegExp(`(\\W|^)${value}(\\W|$)`, 'gm')
const matches = line.match(regex)
if (matches) {
for (const match of matches) {
const lineNumber = i
const startChar = line.indexOf(match) > 0
? line.indexOf(match) + 1
: 0
const endChar = startChar + value.length
const range = new vscode.Range(lineNumber, startChar, lineNumber, endChar);
if (!errors.find(err => err.range.contains(range))) {
errors.push({
range,
message: `Use substitute \`{{${key}}}\` instead of \`${value}\``,
severity: vscode.DiagnosticSeverity.Warning,
code: 'use_sub'
});
}
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for calculating startChar is incorrect. When indexOf(match) > 0, it adds 1 to skip the word boundary character, but when indexOf(match) === 0, it should still account for the actual position of the value within the match. This will cause incorrect highlighting positions.

Suggested change
const regex = new RegExp(`(\\W|^)${value}(\\W|$)`, 'gm')
const matches = line.match(regex)
if (matches) {
for (const match of matches) {
const lineNumber = i
const startChar = line.indexOf(match) > 0
? line.indexOf(match) + 1
: 0
const endChar = startChar + value.length
const range = new vscode.Range(lineNumber, startChar, lineNumber, endChar);
if (!errors.find(err => err.range.contains(range))) {
errors.push({
range,
message: `Use substitute \`{{${key}}}\` instead of \`${value}\``,
severity: vscode.DiagnosticSeverity.Warning,
code: 'use_sub'
});
}
const regex = new RegExp(`(\\W|^)${value}(\\W|$)`, 'g')
let match;
while ((match = regex.exec(line)) !== null) {
const lineNumber = i;
// match[1] is the leading boundary, match.index is the start of the match
const startChar = match.index + (match[1] ? match[1].length : 0);
const endChar = startChar + value.length;
const range = new vscode.Range(lineNumber, startChar, lineNumber, endChar);
if (!errors.find(err => err.range.contains(range))) {
errors.push({
range,
message: `Use substitute \`{{${key}}}\` instead of \`${value}\``,
severity: vscode.DiagnosticSeverity.Warning,
code: 'use_sub'
});

Copilot uses AI. Check for mistakes.

Comment on lines +57 to +58
const filteredSubs = Object.keys(unorderedSubs).filter(sub => {
return !Object.values(PRODUCTS).includes(unorderedSubs[sub])
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Object.values(PRODUCTS).includes() call inside the filter creates a new array on every iteration. Consider creating the values array once outside the loop for better performance.

Suggested change
const filteredSubs = Object.keys(unorderedSubs).filter(sub => {
return !Object.values(PRODUCTS).includes(unorderedSubs[sub])
const productValues = Object.values(PRODUCTS);
const filteredSubs = Object.keys(unorderedSubs).filter(sub => {
return !productValues.includes(unorderedSubs[sub])

Copilot uses AI. Check for mistakes.

Comment on lines +176 to +177
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
Copy link

Copilot AI Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The loop uses indexed iteration but the index i is only used to get the line. Consider using for (const line of lines) for cleaner, more readable code.

Suggested change
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
for (const line of lines) {

Copilot uses AI. Check for mistakes.

@theletterf
Copy link
Collaborator

@colleenmcginnis This is a great idea. It would indeed be fantastic to pull the data directly from docs-builder — this is why I created this proposal here: elastic/docs-builder#1988

Could you have a look at Copilot's comments before I test this myself later this week? Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants