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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# ---------------------------------------------------------------
# To update the sha:
# https://github.com/github/gh-base-image/pkgs/container/gh-base-image%2Fgh-base-noble
FROM ghcr.io/github/gh-base-image/gh-base-noble:20250616-220726-g8823b63b3 AS base
FROM ghcr.io/github/gh-base-image/gh-base-noble:20250617-184854-gd653802be AS base

# Install curl for Node install and determining the early access branch
# Install git for cloning docs-early-access & translations repos
Expand Down
10 changes: 0 additions & 10 deletions src/audit-logs/data/fpt/organization.json
Original file line number Diff line number Diff line change
Expand Up @@ -1139,11 +1139,6 @@
"description": "Autofix for third party tools for code scanning alerts was enabled for an organization.",
"docs_reference_links": "N/A"
},
{
"action": "org_code_security_metered_usage.lock",
"description": "Enablement for Code Security features on new repositories has been locked for this organization.",
"docs_reference_links": "N/A"
},
{
"action": "org.codeql_disabled",
"description": "Code scanning using the default setup was disabled for an organization.",
Expand Down Expand Up @@ -1564,11 +1559,6 @@
"description": "The visibility of a self-hosted runner group was updated via the REST API.",
"docs_reference_links": "/rest/actions#update-a-self-hosted-runner-group-for-an-organization"
},
{
"action": "org_secret_protection_metered_usage.lock",
"description": "Enablement for Secret Protection features on new repositories has been locked for this organization.",
"docs_reference_links": "N/A"
},
{
"action": "org_secret_scanning_automatic_validity_checks.disabled",
"description": "Automatic partner validation checks have been disabled at the organization level",
Expand Down
12 changes: 6 additions & 6 deletions src/audit-logs/data/ghec/enterprise.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@
"description": "GitHub Advanced Security was enabled for new user namespace repositories in your enterprise.",
"docs_reference_links": "/admin/code-security/managing-github-advanced-security-for-your-enterprise/managing-github-advanced-security-features-for-your-enterprise"
},
{
"action": "business.advanced_security_metered_usage_unlock",
"description": "Enablement for Advanced Security features on new repositories has been unlocked for this enterprise.",
"docs_reference_links": "N/A"
},
{
"action": "business.advanced_security_policy_update",
"description": "An enterprise owner created, updated, or removed a policy for GitHub Advanced Security.",
Expand Down Expand Up @@ -240,7 +245,7 @@
"docs_reference_links": "/code-security/getting-started/github-security-features#available-with-github-code-security"
},
{
"action": "business_code_security_metered_usage.lock",
"action": "business.code_security_metered_usage_lock",
"description": "Enablement for Code Security features on new repositories has been locked for this enterprise.",
"docs_reference_links": "N/A"
},
Expand Down Expand Up @@ -489,11 +494,6 @@
"description": "The SAML single sign-on session for a member in an enterprise was revoked.",
"docs_reference_links": "N/A"
},
{
"action": "business_secret_protection_metered_usage.lock",
"description": "Enablement for Secret Protection features on new repositories has been locked for this enterprise.",
"docs_reference_links": "N/A"
},
{
"action": "business_secret_scanning_automatic_validity_checks.disabled",
"description": "Automatic partner validation checks have been disabled at the business level",
Expand Down
10 changes: 0 additions & 10 deletions src/audit-logs/data/ghec/organization.json
Original file line number Diff line number Diff line change
Expand Up @@ -1139,11 +1139,6 @@
"description": "Autofix for third party tools for code scanning alerts was enabled for an organization.",
"docs_reference_links": "N/A"
},
{
"action": "org_code_security_metered_usage.lock",
"description": "Enablement for Code Security features on new repositories has been locked for this organization.",
"docs_reference_links": "N/A"
},
{
"action": "org.codeql_disabled",
"description": "Code scanning using the default setup was disabled for an organization.",
Expand Down Expand Up @@ -1564,11 +1559,6 @@
"description": "The visibility of a self-hosted runner group was updated via the REST API.",
"docs_reference_links": "/rest/actions#update-a-self-hosted-runner-group-for-an-organization"
},
{
"action": "org_secret_protection_metered_usage.lock",
"description": "Enablement for Secret Protection features on new repositories has been locked for this organization.",
"docs_reference_links": "N/A"
},
{
"action": "org_secret_scanning_automatic_validity_checks.disabled",
"description": "Automatic partner validation checks have been disabled at the organization level",
Expand Down
2 changes: 1 addition & 1 deletion src/audit-logs/lib/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"apiOnlyEvents": "This event is not available in the web interface, only via the REST API, audit log streaming, or JSON/CSV exports.",
"apiRequestEvent": "This event is only available via audit log streaming."
},
"sha": "bce8d9394f9f4e9dcd9d1661a53735ddebc8bd8c"
"sha": "eba0af9fafae7ac460e7063e153b06b7bc5ab300"
}
99 changes: 99 additions & 0 deletions src/content-linter/lib/linting-rules/british-english-quotes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { addError } from 'markdownlint-rule-helpers'
import { getRange } from '../helpers/utils.js'
import frontmatter from '#src/frame/lib/read-frontmatter.js'

export const britishEnglishQuotes = {
names: ['GHD048', 'british-english-quotes'],
description:
'Periods and commas should be placed inside quotation marks (American English style)',
tags: ['punctuation', 'quotes', 'style', 'consistency'],
severity: 'warning', // Non-blocking as requested in the issue
function: (params, onError) => {
// Skip autogenerated files
const frontmatterString = params.frontMatterLines.join('\n')
const fm = frontmatter(frontmatterString).data
if (fm && fm.autogenerated) return

// Check each line for British English quote patterns
for (let i = 0; i < params.lines.length; i++) {
const line = params.lines[i]
const lineNumber = i + 1

// Skip code blocks, code spans, and URLs
if (isInCodeContext(line, params.lines, i)) {
continue
}

// Find British English quote patterns and report them
findAndReportBritishQuotes(line, lineNumber, onError)
}
},
}

/**
* Check if the current position is within a code context (code blocks, inline code, URLs)
*/
function isInCodeContext(line, allLines, lineIndex) {
// Skip if line contains code fences
if (line.includes('```') || line.includes('~~~')) {
return true
}

// Check if we're inside a code block
let inCodeBlock = false
for (let i = 0; i < lineIndex; i++) {
if (allLines[i].includes('```') || allLines[i].includes('~~~')) {
inCodeBlock = !inCodeBlock
}
}
if (inCodeBlock) {
return true
}

// Skip if line appears to be mostly code (has multiple backticks)
const backtickCount = (line.match(/`/g) || []).length
if (backtickCount >= 4) {
return true
}

// Skip URLs and email addresses
if (line.includes('http://') || line.includes('https://') || line.includes('mailto:')) {
return true
}

return false
}

/**
* Find and report British English quote patterns in a line
*/
function findAndReportBritishQuotes(line, lineNumber, onError) {
// Pattern to find quote followed by punctuation outside
// Matches: "text". or 'text', or "text", etc.
const britishPattern = /(["'])([^"']*?)\1\s*([.,])/g

let match
while ((match = britishPattern.exec(line)) !== null) {
const quoteChar = match[1]
const quotedText = match[2]
const punctuation = match[3]
const fullMatch = match[0]
const startIndex = match.index

// Create the corrected version (punctuation inside quotes)
const correctedText = quoteChar + quotedText + punctuation + quoteChar

const range = getRange(line, fullMatch)
const punctuationName = punctuation === '.' ? 'period' : 'comma'
const errorMessage = `Use American English punctuation: place ${punctuationName} inside the quotation marks`

// Provide auto-fix
const fixInfo = {
editColumn: startIndex + 1,
deleteCount: fullMatch.length,
insertText: correctedText,
}

addError(onError, lineNumber, errorMessage, line, range, fixInfo)
}
}
4 changes: 4 additions & 0 deletions src/content-linter/lib/linting-rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { liquidTagWhitespace } from './liquid-tag-whitespace.js'
import { linkQuotation } from './link-quotation.js'
import { octiconAriaLabels } from './octicon-aria-labels.js'
import { liquidIfversionVersions } from './liquid-ifversion-versions.js'
import { britishEnglishQuotes } from './british-english-quotes.js'
import { multipleEmphasisPatterns } from './multiple-emphasis-patterns.js'
import { noteWarningFormatting } from './note-warning-formatting.js'

const noDefaultAltText = markdownlintGitHub.find((elem) =>
Expand Down Expand Up @@ -85,6 +87,8 @@ export const gitHubDocsMarkdownlint = {
liquidTagWhitespace,
linkQuotation,
octiconAriaLabels,
britishEnglishQuotes,
multipleEmphasisPatterns,
noteWarningFormatting,
],
}
93 changes: 93 additions & 0 deletions src/content-linter/lib/linting-rules/multiple-emphasis-patterns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { addError } from 'markdownlint-rule-helpers'
import { getRange } from '../helpers/utils.js'
import frontmatter from '#src/frame/lib/read-frontmatter.js'

export const multipleEmphasisPatterns = {
names: ['GHD050', 'multiple-emphasis-patterns'],
description: 'Do not use more than one emphasis/strong, italics, or uppercase for a string',
tags: ['formatting', 'emphasis', 'style'],
severity: 'warning',
function: (params, onError) => {
// Skip autogenerated files
const frontmatterString = params.frontMatterLines.join('\n')
const fm = frontmatter(frontmatterString).data
if (fm && fm.autogenerated) return

const lines = params.lines
let inCodeBlock = false

for (let i = 0; i < lines.length; i++) {
const line = lines[i]
const lineNumber = i + 1

// Track code block state
if (line.trim().startsWith('```')) {
inCodeBlock = !inCodeBlock
continue
}

// Skip code blocks and indented code
if (inCodeBlock || line.trim().startsWith(' ')) continue

// Check for multiple emphasis patterns
checkMultipleEmphasis(line, lineNumber, onError)
}
},
}

/**
* Check for multiple emphasis types in a single text segment
*/
function checkMultipleEmphasis(line, lineNumber, onError) {
// Focus on the clearest violations of the style guide
const multipleEmphasisPatterns = [
// Bold + italic combinations (***text***)
{ regex: /\*\*\*([^*]+)\*\*\*/g, types: ['bold', 'italic'] },
{ regex: /___([^_]+)___/g, types: ['bold', 'italic'] },

// Bold with code nested inside
{ regex: /\*\*([^*]*`[^`]+`[^*]*)\*\*/g, types: ['bold', 'code'] },
{ regex: /__([^_]*`[^`]+`[^_]*)__/g, types: ['bold', 'code'] },

// Code with bold nested inside
{ regex: /`([^`]*\*\*[^*]+\*\*[^`]*)`/g, types: ['code', 'bold'] },
{ regex: /`([^`]*__[^_]+__[^`]*)`/g, types: ['code', 'bold'] },
]

for (const pattern of multipleEmphasisPatterns) {
let match
while ((match = pattern.regex.exec(line)) !== null) {
// Skip if this is likely intentional or very short
if (shouldSkipMatch(match[0], match[1])) continue

const range = getRange(line, match[0])
addError(
onError,
lineNumber,
`Do not use multiple emphasis types in a single string: ${pattern.types.join(' + ')}`,
line,
range,
null, // No auto-fix as this requires editorial judgment
)
}
}
}

/**
* Determine if a match should be skipped (likely intentional formatting)
*/
function shouldSkipMatch(fullMatch, content) {
// Skip common false positives
if (!content) return true

// Skip very short content (likely intentional single chars)
if (content.trim().length < 2) return true

// Skip if it's mostly code-like content (constants, variables)
if (/^[A-Z_][A-Z0-9_]*$/.test(content.trim())) return true

// Skip file extensions or URLs
if (/\.[a-z]{2,4}$/i.test(content.trim()) || /https?:\/\//.test(content)) return true

return false
}
Loading
Loading