Skip to content

Commit

Permalink
Allow leading underscore in sketch filenames
Browse files Browse the repository at this point in the history
The Arduino Sketch Specification defines the allowed format of sketch folder names and sketch code filenames. Arduino
IDE enforces compliance with the specification in order to ensure sketches created with Arduino IDE can be used with any
other Arduino development tool.

The Arduino Sketch Specification has been changed to allow a leading underscore in sketch folder names and sketch code
filenames so IDE's sketch name validation must be updated accordingly.
  • Loading branch information
per1234 committed Mar 13, 2023
1 parent ec24b68 commit 58aac23
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 39 deletions.
20 changes: 6 additions & 14 deletions arduino-ide-extension/src/common/protocol/sketches-service.ts
Expand Up @@ -157,8 +157,6 @@ export namespace Sketch {
// (non-API) exported for the tests
export const defaultSketchFolderName = 'sketch';
// (non-API) exported for the tests
export const defaultFallbackFirstChar = '0';
// (non-API) exported for the tests
export const defaultFallbackChar = '_';
// (non-API) exported for the tests
export function reservedFilename(name: string): string {
Expand All @@ -176,11 +174,11 @@ export namespace Sketch {
// (non-API) exported for the tests
export const invalidSketchFolderNameMessage = nls.localize(
'arduino/sketch/invalidSketchName',
'The name must start with a letter or number, followed by letters, numbers, dashes, dots and underscores. Maximum length is 63 characters.'
'The name must start with a letter, number, or underscore, followed by letters, numbers, dashes, dots and underscores. Maximum length is 63 characters.'
);
const invalidCloudSketchFolderNameMessage = nls.localize(
'arduino/sketch/invalidCloudSketchName',
'The name must start with a letter or number, followed by letters, numbers, dashes, dots and underscores. Maximum length is 36 characters.'
'The name must start with a letter, number, or underscore, followed by letters, numbers, dashes, dots and underscores. Maximum length is 36 characters.'
);
/**
* `undefined` if the candidate sketch folder name is valid. Otherwise, the validation error message.
Expand All @@ -193,7 +191,7 @@ export namespace Sketch {
if (validFilenameError) {
return validFilenameError;
}
return /^[0-9a-zA-Z]{1}[0-9a-zA-Z_\.-]{0,62}$/.test(candidate)
return /^[0-9a-zA-Z_]{1}[0-9a-zA-Z_\.-]{0,62}$/.test(candidate)
? undefined
: invalidSketchFolderNameMessage;
}
Expand All @@ -208,7 +206,7 @@ export namespace Sketch {
if (validFilenameError) {
return validFilenameError;
}
return /^[0-9a-zA-Z]{1}[0-9a-zA-Z_\.-]{0,35}$/.test(candidate)
return /^[0-9a-zA-Z_]{1}[0-9a-zA-Z_\.-]{0,35}$/.test(candidate)
? undefined
: invalidCloudSketchFolderNameMessage;
}
Expand Down Expand Up @@ -252,10 +250,7 @@ export namespace Sketch {
return defaultSketchFolderName;
}
const validName = candidate
? candidate
.replace(/^[^0-9a-zA-Z]{1}/g, defaultFallbackFirstChar)
.replace(/[^0-9a-zA-Z_]/g, defaultFallbackChar)
.slice(0, 63)
? candidate.replace(/[^0-9a-zA-Z_]/g, defaultFallbackChar).slice(0, 63)
: defaultSketchFolderName;
if (appendTimestampSuffix) {
return `${validName.slice(0, 63 - timestampSuffixLength)}${
Expand Down Expand Up @@ -283,10 +278,7 @@ export namespace Sketch {
return defaultSketchFolderName;
}
return candidate
? candidate
.replace(/^[^0-9a-zA-Z]{1}/g, defaultFallbackFirstChar)
.replace(/[^0-9a-zA-Z_]/g, defaultFallbackChar)
.slice(0, 36)
? candidate.replace(/[^0-9a-zA-Z_]/g, defaultFallbackChar).slice(0, 36)
: defaultSketchFolderName;
}

Expand Down
Expand Up @@ -171,22 +171,22 @@ describe('workspace-commands', () => {
});

it('code files cannot start with number (no extension)', async () => {
const actual = await testMe('_invalid');
const actual = await testMe('-invalid');
expect(actual).to.be.equal(Sketch.invalidSketchFolderNameMessage);
});

it('code files cannot start with number (trailing dot)', async () => {
const actual = await testMe('_invalid.');
const actual = await testMe('-invalid.');
expect(actual).to.be.equal(Sketch.invalidSketchFolderNameMessage);
});

it('code files cannot start with number (trailing dot)', async () => {
const actual = await testMe('_invalid.cpp');
const actual = await testMe('-invalid.cpp');
expect(actual).to.be.equal(Sketch.invalidSketchFolderNameMessage);
});

it('should warn about invalid extension first', async () => {
const actual = await testMe('_invalid.xxx');
const actual = await testMe('-invalid.xxx');
expect(actual).to.be.equal(invalidExtensionMessage('.xxx'));
});

Expand All @@ -196,7 +196,7 @@ describe('workspace-commands', () => {
});

it('should ignore non-code filename validation from the spec', async () => {
const actual = await testMe('_invalid.json');
const actual = await testMe('-invalid.json');
expect(actual).to.be.empty;
});

Expand Down
32 changes: 14 additions & 18 deletions arduino-ide-extension/src/test/common/sketches-service.test.ts
Expand Up @@ -43,7 +43,7 @@ describe('sketch', () => {
['trailing.dots...', false],
['no.trailing.dots.._', true],
['No Spaces', false],
['_invalidToStartWithUnderscore', false],
['_validToStartWithUnderscore', true],
['Invalid+Char.ino', false],
['', false],
['/', false],
Expand Down Expand Up @@ -81,7 +81,7 @@ describe('sketch', () => {
['can.contain.dots', true],
['-cannot-start-with-dash', false],
['.cannot.start.with.dash', false],
['_cannot_start_with_underscore', false],
['_can_start_with_underscore', true],
['No Spaces', false],
['Invalid+Char.ino', false],
['', false],
Expand All @@ -108,14 +108,14 @@ describe('sketch', () => {
describe('toValidSketchFolderName', () => {
[
['', Sketch.defaultSketchFolderName],
[' ', Sketch.defaultFallbackFirstChar],
[' ', Sketch.defaultFallbackFirstChar + Sketch.defaultFallbackChar],
[' ', Sketch.defaultFallbackChar],
[' ', Sketch.defaultFallbackChar + Sketch.defaultFallbackChar],
[
'0123456789012345678901234567890123456789012345678901234567890123',
'012345678901234567890123456789012345678901234567890123456789012',
],
['foo bar', 'foo_bar'],
['_foobar', '0foobar'],
['-foobar', '_foobar'],
['vAlid', 'vAlid'],
['COM1', Sketch.defaultSketchFolderName],
['COM1.', 'COM1_'],
Expand All @@ -130,20 +130,18 @@ describe('sketch', () => {
const epochSuffix = Sketch.timestampSuffix(epoch);
[
['', Sketch.defaultSketchFolderName + epochSuffix],
[' ', Sketch.defaultFallbackFirstChar + epochSuffix],
[' ', Sketch.defaultFallbackChar + epochSuffix],
[
' ',
Sketch.defaultFallbackFirstChar +
Sketch.defaultFallbackChar +
epochSuffix,
Sketch.defaultFallbackChar + Sketch.defaultFallbackChar + epochSuffix,
],
[
'0123456789012345678901234567890123456789012345678901234567890123',
'0123456789012345678901234567890123456789012' + epochSuffix,
],
['foo bar', 'foo_bar' + epochSuffix],
['.foobar', '0foobar' + epochSuffix],
['-fooBar', '0fooBar' + epochSuffix],
['.foobar', '_foobar' + epochSuffix],
['-fooBar', '_fooBar' + epochSuffix],
['foobar.', 'foobar_' + epochSuffix],
['fooBar-', 'fooBar_' + epochSuffix],
['fooBar+', 'fooBar_' + epochSuffix],
Expand All @@ -164,19 +162,17 @@ describe('sketch', () => {
['only_underscore-is+ok.ino', 'only_underscore_is_ok_ino'],
['regex++', 'regex__'],
['dots...', 'dots___'],
['.dots...', '0dots___'],
['-dashes---', '0dashes___'],
['_underscore___', '0underscore___'],
['.dots...', '_dots___'],
['-dashes---', '_dashes___'],
['No Spaces', 'No_Spaces'],
['_startsWithUnderscore', '0startsWithUnderscore'],
['Invalid+Char.ino', 'Invalid_Char_ino'],
['', 'sketch'],
['/', '0'],
['/', '_'],
[
'/-1////////////////////+//////////////-/',
'0_1_________________________________',
'__1_________________________________',
],
['//trash/', '0_trash_'],
['//trash/', '__trash_'],
[
'63Length_012345678901234567890123456789012345678901234567890123',
'63Length_012345678901234567890123456',
Expand Down
4 changes: 2 additions & 2 deletions i18n/en.json
Expand Up @@ -419,11 +419,11 @@
"editInvalidSketchFolderLocationQuestion": "Do you want to try saving the sketch to a different location?",
"editInvalidSketchFolderQuestion": "Do you want to try saving the sketch with a different name?",
"exportBinary": "Export Compiled Binary",
"invalidCloudSketchName": "The name must start with a letter or number, followed by letters, numbers, dashes, dots and underscores. Maximum length is 36 characters.",
"invalidCloudSketchName": "The name must start with a letter, number, or underscore, followed by letters, numbers, dashes, dots and underscores. Maximum length is 36 characters.",
"invalidSketchFolderLocationDetails": "You cannot save a sketch into a folder inside itself.",
"invalidSketchFolderLocationMessage": "Invalid sketch folder location: '{0}'",
"invalidSketchFolderNameMessage": "Invalid sketch folder name: '{0}'",
"invalidSketchName": "The name must start with a letter or number, followed by letters, numbers, dashes, dots and underscores. Maximum length is 63 characters.",
"invalidSketchName": "The name must start with a letter, number, or underscore, followed by letters, numbers, dashes, dots and underscores. Maximum length is 63 characters.",
"moving": "Moving",
"movingMsg": "The file \"{0}\" needs to be inside a sketch folder named \"{1}\".\nCreate this folder, move the file, and continue?",
"new": "New Sketch",
Expand Down

0 comments on commit 58aac23

Please sign in to comment.